home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-08-28 | 184.8 KB | 3,867 lines | [TEXT/MPS ] |
- /*
- File: Update.c
-
- Contains: Container Manager Container Updating Routines
-
- Owned by: Ira L. Ruben
-
- Owned by: Ed Lai
-
- Copyright: © 1992 - 1996 by Apple Computer, Inc., all rights reserved.
-
- Change History (most recent first):
-
- <2> 8/13/96 DM 1362809: disable containers on error
- <9> 10/9/95 EL 1289916: when touch data struct is freed,
- valueHdr should no longer point to touch
- because this is a problem in merge where
- valueHdr is not freed and can point to
- garbage now. 1290781: avoid generating
- extra delete property opocde. Ignore opcode
- if target object or property cannot be
- found.
- <8> 5/25/95 jpa Fixed minor typecheck mistake exposed by
- new SC compiler. [1241078]
- <7> 5/11/95 DM 1214394, 1245829: stop compiler warnings
- <6> 12/9/94 EL #1182275 Optionally do not maintain
- continue flag. When updates are merged,
- past deleted objects are gone, so we cannot
- depend to get them from touch list.
- <5> 10/17/94 EL #1193668 Fix bug where offsetInfoPtr in
- applyValueUpdateInstructions can be
- uninitialized.
- <4> 9/30/94 EL #1182275 rename mergeCandidate to
- updateMergeCandidate.
- <3> 9/15/94 EL #1182275 Add cmClosePastDataGap.
- <2> 8/26/94 EL #1182275 Merge of one updating container to
- another updating container. #1182308 Do not
- do endian-ness byte flipping for immediate
- data in update object.
- <7> 6/3/94 EL Rename Values.h/c to ValueRtn.h/c.
- <6> 6/2/94 EL #1156779. Fix insertion of data into
- immediate value.
- <5> 5/5/94 EL Post alpha fix to the bug. #1156779.
- <4> 4/18/94 EL Yet another fix to the same bug. #1156779
- <3> 4/13/94 EL Previous fix does not work for non-zero
- length data. #1156779
- <2> 4/13/94 EL Allow replacement of non-immediate data in
- target by immediate data. #1156779
- <1> 2/3/94 EL first checked in
- <3> 10/29/93 EL Add extra parameter to cmDeleteTouchList
- and export cmDeleteObjeFromTouchedChain
- <2> 10/21/93 EL Record delete last value as delete
- property. Add debug dumping of update
- opcodes.
-
- To Do:
- Merging of SetInfoedValue.
- In Progress:
-
- */
-
- /*---------------------------------------------------------------------------*
- | |
- | <<< Update.c >>> |
- | |
- | Container Manager Container Updating Routines |
- | |
- | Ira L. Ruben |
- | 7/10/92 |
- | |
- | Copyright Apple Computer, Inc. 1992-1994 |
- | All rights reserved. |
- | |
- *---------------------------------------------------------------------------*
-
- This file includes almost all the "incremental update" processing routines when one
- updating container updates its target container. The routines in here are responsible
- for maintaining the "touched chain" and "touched lists". These are additional data
- structures layered on to the main data structures (see TOCEntries.[hc]) as described in
- the following (abbreviated) diagram:
-
-
- *---------*
- | CCB | Touched Chain
- | |--------->---------+
- *---------* |
- |
- |
- +------------<-------------+ |
- | | |
- *----*<--+ +------*----*<--+
- | O1 | *-----* | O2 | *-----*
- | |--------->| P11 |- - -> | |--------->| P21 |- - ->
- *----* *-----* *----* *-----*
- | | | |
- Touched List | Touched List |
- | *-------* | *-------*
- *---------*<--+ |VH(T11)| *---------*<----->|VH(T21)|
- |O1 P1 T12| | *-------* |O2 P2 T21| *-------*
- | [flags] | | | | [flags] | |
- *---------* | | *---------* |
- | | *-------* | *-------*
- | +-->|VH(T12)| | |VH(T22)|
- | *-------* | *-------*
- *---------* | *---------*<--+ |
- |O1 P1 T13| | |O1 P1 T12| | |
- | removed | ''''''''' |inserted | | *-------*
- *---------* 'VH(T13)' *---------* +-->|VH(T13)|
- ''''''''' *-------*
- . .
- ............................................
-
-
- This diagram shows parts of two objects, O1 and O2, with at least one property each, P11
- and P12 respectively. Each property has two value (headers) of their own of the indicated
- types. The value segments and all links details for this stuff are not shown. Value
- header VH(T13) is shown moved from O1/P11 to O2/P21.
-
- The "Touched Chain"
-
- This is a singly linked chain of all objects (through the TOCObjects) that are "touched"
- during the updating session. The head of this chain is contained in the Container Control
- Block (CCB). The order of updating recording does not matter, so the most convenient way
- to link this list is backward. Thus in the above diagram object O1 was touched before O2.
-
- The "Touched List"
-
- This is a doubly linked list whose header is in the object (TOCObject). A doubly linked
- list is used to make it easy to remove or move these entries. List entries are generated
- for each value updated. There is only one list entry per value no matter how many times
- the corresponding value is updated. The value header for an updated value points back to
- its touched list entry. This pointer is initially NULL and doubles as a "first touched"
- switch.
-
- Each touched list entry contains the following:
-
- (a). The original object, property, and type IDs which we refer to as the "OPT address".
- The OPTs will work their way into the updating instructions generated at close time
- for use in addressing the original values at open time. They uniquely address the
- original value prior to applying any updates to properly apply those updates.
-
- (b). A pointer to the value header that was touched. Thus the touched list entry and its
- value header point to each other.
-
- (c). A "back pointer" to an initial touched list entry for moved values. The use of this
- pointer is discussed later. For unmoved touched values, this pointer is NULL.
-
- (d). Flags to indicate special conditions of the touched entry. These conditions are:
-
- -> "Removed" The value has been moved to another object. The touched list
- entry for the original "from" object's value is created or
- flagged as "removed".
-
- -> "Inserted" The value has been moved from another or the same object. A
- touched list entry for the destination object is created and
- marked as "inserted". If it is the same object and previously
- touched, the touched list entry is changed to "inserted".
-
- -> "Deleted Value" The value has been deleted.
-
- -> "Deleted Property" The property specified by address "OP" has been deleted.
-
- Except for the "deleted property" flag, all flags deal with moved values.
-
- The only things not in this file that deal with updating are the open-time and close-time
- processing dealing with connecting and detaching the updating container from its target.
- Most everything else is here.
-
- Enjoy...
- */
-
- #include <stddef.h>
- #include <string.h>
- #include <stdio.h>
- #include <setjmp.h>
-
- #ifndef __CMTYPES__
- #include "CMTypes.h"
- #endif
- #ifndef __CM_API_TYPES__
- #include "CMAPITyp.h"
- #endif
- #ifndef __CM_API__
- #include "CMAPI.h"
- #endif
- #ifndef __LISTMGR__
- #include "ListMgr.h"
- #endif
- #ifndef __VALUEROUTINES__
- #include "ValueRtn.h"
- #endif
- #ifndef __TOCENTRIES__
- #include "TOCEnts.h"
- #endif
- #ifndef __TOCOBJECTS__
- #include "TOCObjs.h"
- #endif
- #ifndef __CONTAINEROPS__
- #include "Containr.h"
- #endif
- #ifndef __HANDLERS__
- #include "Handlers.h"
- #endif
- #ifndef __FREESPACE__
- #include "FreeSpce.h"
- #endif
- #ifndef __UPDATING__
- #include "Update.h"
- #endif
- #ifndef __BUFFEREDIO__
- #include "BufferIO.h"
- #endif
- #ifndef __SESSIONDATA__
- #include "Session.h"
- #endif
- #ifndef __ERRORRPT__
- #include "ErrorRpt.h"
- #endif
- #ifndef __UTILITYROUTINES__
- #include "Utility.h"
- #endif
-
- CM_CFUNCTIONS
-
- /* The following generates a segment directive for Mac only due to 32K Jump Table */
- /* Limitations. If you don't know what I'm talking about don't worry about it. The */
- /* default is not to generate the pragma. Theoritically unknown pragmas in ANSI won't */
- /* choke compilers that don't recognize them. Besides why would they be looked at if */
- /* it's being conditionally skipped over anyway? I know, famous last words! */
-
- #if CM_MPW
- #pragma segment Updating
- #endif
-
- #define debugUpdateList 0
- #define CMIGNORE_NONFATAL 1
-
-
- struct PastUpdate { /* Layout of a update which had been applied: */
- ListLinks updateLinks; /* other update instr links (must be first)*/
- CMObjectID objectID; /* value's original object ID */
- CMObjectID propertyID; /* " " property ID */
- CMObjectID typeID; /* " " type ID */
- CM_CHAR ctlByte; /* type of update */
- };
- typedef struct PastUpdate PastUpdate;
-
- struct PastDeleteData {
- PastUpdate updateHeader; /* The OPT of the Value */
- CM_ULONG offset;
- CM_ULONG size;
- };
- typedef struct PastDeleteData PastDeleteData;
-
- struct PastInsertData {
- PastUpdate updateHeader; /* The OPT of the Value */
- CM_ULONG insertPoint;
- CM_ULONG insertCount;
- CM_ULONG offset;
- CM_ULONG size;
- };
- typedef struct PastInsertData PastInsertData;
-
- struct OffsetSizePair {
- CM_ULONG offset;
- CM_ULONG size;
- };
- typedef struct OffsetSizePair OffsetSizePair;
-
- struct PastReplaceImmediate {
- PastUpdate updateHeader; /* The OPT of the Value */
- CM_ULONG size;
- CM_ULONG newImmediate;
- };
- typedef struct PastReplaceImmediate PastReplaceImmediate;
-
- struct PastSetInfoedValue {
- PastUpdate updateHeader; /* The OPT of the Value */
- CMObjectID newType;
- CMGeneration generation;
- };
- typedef struct PastSetInfoedValue PastSetInfoedValue;
-
- struct PastReplaceBaseType {
- PastUpdate updateHeader; /* The OPT of the Value */
- CM_USHORT nBases;
- };
- typedef struct PastReplaceBaseType PastReplaceBaseType;
-
- /*---------------------------------------------*
- | File Organization (yes, this file has one!) |
- *---------------------------------------------*
-
- To aid those reading this file, and who aren't using MPW and its marker facility (if you
- don't know what markers are keep reading) the following list of function names summarizes
- the grouping organization in thei file. This may make it easier to see the breakdown of
- this file to understand it.
-
- Miscellaneoush utilities
- ------------------------
- cmAddObjToTouchedChain - add an object on to the container's touched chain
- deleteObjFromTouchedChain - delete an object from the container's touched chain
- createTouchedListEntry - create a touched list entry for a value
- cmDeleteTouchedList - delete entire touched list for an object
-
- Simple Touches (touch list creation)
- ------------------------------------
- simpleValueTouch - generate touched list entry for a "simple" touch
- cmTouchEditedValue - generate a touched list entry for an edited value
- cmTouchImmediateValue - generate a touched list entry for a modified immediate
- cmTouchBaseType - generate a touched list entry for a modified base type
- cmTouchSetInfoedValue - generate a touched list entry for a "set-infoed" value
-
- Complex Touches (touch list creation)
- -------------------------------------
- cmTouchMovedValue - generate touched lists for a value being moved
- cmTouchDeletedValue - generate touched list entry for value being deleted
- cmImplicitDeleteValueTouch - delete touched list entry for an implicitly deleted value
- cmTouchDeletedProperty - generate a touched list entry for a deleted property
- cmTouchDeletedObject - generate a touched list entry for a deleted object
-
- Update Instruction Generation (output)
- --------------------------------------
- generateDataEdits - generate insert/delete (data editing) instructions
- genValueUpdateInstructions - generate value instructions
- generateValueUpdates - main control generate value instructions
- generateDeleteUpdates - generate object and property delete instructions
- cmGenerateUpdates - main control for update instruction generation
-
- Update Instruction Interpretation (input)
- -----------------------------------------
- applyValueUpdateInstructions - value updating instruction interpreter
- applyValueUpdates - main control interpreter to apply value updating instructions
- applyDeleteUpdates - interpreter to apply object/property deletion instructions
- cmApplyUpdates - apply all updates to bring a target "up to date"
- */
-
-
- /*------------------------*
- | Updating Control Codes |
- *------------------------*/
-
- /* The following define the control code values generated in the updating instructions */
- /* written to the container. See code that generates the actual data for the exact */
- /* instruction format layout. */
-
- #define EndUpdates 0xFFU /* end of updating instruction sequence */
-
- /* ***The following are main control codes***/
- #define NewProperty 1U /* new property (type follows) */
- #define NewType 2U /* new type, same prop. (value ctl follows) */
- #define DeleteObject 3U /* delete entire object */
- #define DeleteProperty1 4U /* delete property within object */
- #define DeleteProperty2 5U /* delete property for current object */
-
- /* ***The following are value update codes***/
- #define RemovedValue 6U /* remove value from object */
- #define InsertedValue 7U /* insert a value from somplace into object */
- #define SetInfoedValue 8U /* change type and generation */
- #define DeleteData 9U /* delete data */
- #define InsertData1 10U /* insert data (one segment) */
- #define InsertDataN 11U /* insert data (N segments) */
- #define ReplaceImmediate 12U /* replace with new immediate data */
- #define ReplaceBaseType 13U /* replace with new base type data */
- #define DeleteValue 14U /* delete value */
-
- #define ErrorCtl 15U /* used only for error recover during input */
-
-
- /*----------------------------------------------------------------------------*
- | cmAddObjToTouchedChain - add an object on to the container's touched chain |
- *----------------------------------------------------------------------------*
-
- The TOCObject pointed to by theObject is "touched", i.e., it is put on the updating
- container's singly linked chain of touched objects, the touchedChain.
-
- The container is passed because the touched list has to be attached to a "know fixed"
- container. The fixed container is usually the updatingContainer. But this needs to be
- "controlled" during open time because the updatingContainer is also used define the
- "owner" of NEW objects read in during TOC loading. The updatingContainer then is the
- container doing the loading, NOT the original container the user opened.
-
- cmReadTOC(), who loads the TOC, calls this routine (which is why it is "exposed" outside
- this file) to add objects to the touched chain that have the special "updates" property.
- Such objects have associated updating instructions (generated by generateValueUpdates()
- at close time). These must be chained together until the TOC is fully read in. This is
- because "inserted" (i.e., move) updates could refer to objects not yet read in. The
- touched chain is a convenient place! So we use it and this routine to build the chain.
- This chain is only temporary. It is "fed" to cmApplyUpdates() who walks the chain to
- apply all the updates. The chain is then immediately cleared ready to receive new updates
- in the "normal" way.
-
- Note, the object is placed on the touched chain only once. This routine is, however,
- called whenever we think we need to touch the object. We worry about the multiple
- touches here.
- */
-
- void cmAddObjToTouchedChain(ContainerPtr container, TOCObjectPtr theObject)
- {
- if ((theObject->objectFlags & TouchedObject) == 0) { /* if this is 1st touch... */
- theObject->objectFlags |= TouchedObject; /* ...mark object as touched*/
- theObject->nextTouchedObject = container->touchedChain; /* ...link object into chain*/
- container->touchedChain = theObject;
- }
- }
-
- /*---------------------------------------------------------------------------------*
- | deleteObjFromTouchedChain - delete an object from the container's touched chain |
- *---------------------------------------------------------------------------------*
-
- The TOCObject pointed to by theObject is "untouched", i.e., it is removed from the
- updating container's touched chain. The pointer to the previous object on this chain is
- also passed. If it is NULL, then the previous value is assumed to be the touchedChain
- list header in the updating container.
-
- The container is passed as an explicit parameter for the same reason it is in
- cmAddObjToTouchedChain(). See its comments for details.
-
- Note, the object is removed only once. A untouched object has its touched chain link
- set to NULL. This routine resets the link to NULL and makes sure we don't try to unlink
- it twice.
-
- Just as a reminder in case it's not clear how to use this thing, in particular, how to
- manage thePrevObject...
-
- The touched chain is a singly linked list whose header, touchedChain, is in the container
- control block. If an object is to be removed from from the chain, thePrevObject should
- KEEP its current value. This could be NULL as long as the entries starting with the
- first continue to be deleted. As soon as one is retained, then thePrevObject should be
- set to point at that one.
-
- In summary, don't change thePrevObject if this routine is called, and set thePrevObject
- to each retained object at the time it's encountered. Enough said...
-
- This has been enhanced so that if you don't know thePrevObject, just pass in NULL and it
- would search the link list for the previous object. Since this would not be efficent it
- it better off to remember thePrevObject if you can.
- */
-
- void cmDeleteObjFromTouchedChain(ContainerPtr container,
- TOCObjectPtr theObject,
- TOCObjectPtr thePrevObject)
- {
- if (thePrevObject == NULL) /* ...unlink it from chain */
- if (container->touchedChain == theObject)
- container->touchedChain = theObject->nextTouchedObject;
- else {
- thePrevObject = container->touchedChain;
- while (thePrevObject) {
- if (thePrevObject->nextTouchedObject == theObject) {
- thePrevObject->nextTouchedObject = theObject->nextTouchedObject;
- break;
- } else
- thePrevObject = thePrevObject->nextTouchedObject;
- }
- }
- else if (thePrevObject->nextTouchedObject == theObject)
- thePrevObject->nextTouchedObject = theObject->nextTouchedObject;
-
- theObject->nextTouchedObject = NULL; /* ...(un)mark as touched */
- theObject->objectFlags &= ~TouchedObject;
-
- }
-
-
- /*-------------------------------------------------------------------*
- | createTouchedListEntry - create a touched list entry for a value |
- *-------------------------------------------------------------------*
-
- A touched list entry for the value's object is created for the first touch of the
- specified value (header). The value header is set to point at the touched list entry and
- vice versa. The original object, property, and type ID (the "OPT address") are saved in
- the touched list entry as well. The touchFlags are initialized to 0 and must be set by
- the caller.
-
- The function returns the pointer to the value's corresponding touched list entry. NULL
- is returned, and an error reported if there is an allocation error for a new touched
- list entry.
-
- Note, if there were no previous touch list entries for the object, the object is put on
- the updating container's touched chain. If the value was previously touched, the pointer
- to the already existing touched list entry is returned.
-
- Also note, this routine is only for creating touched list entries for values (headers).
- The case for a deleted property touch is handled separately in cmTouchDeletedProperty().
- */
-
- static TouchedListEntryPtr CM_NEAR createTouchedListEntry(TOCValueHdrPtr theValueHdr)
- {
- ContainerPtr container;
- TOCObjectPtr theObject;
- TouchedListEntryPtr touch;
-
- if (theValueHdr->touch != NULL) /* if not first touch... */
- return (theValueHdr->touch); /* ...return corresponding touched entry*/
-
- container = theValueHdr->container->updatingContainer; /* allocate new touched entry */
-
- if ((touch = (TouchedListEntryPtr)CMmalloc(sizeof(TouchedListEntry))) == NULL) {
- Container_Disable(container);
- ERROR1(CM_err_NoTouchedEntry, CONTAINERNAME);
- return (NULL);
- }
-
- theValueHdr->touch = touch; /* point hdr at its touched entry */
- theObject = theValueHdr->theProperty->theObject;
-
- cmNullListLinks(touch); /* init touched list entry fields... */
- touch->theValueHdr = theValueHdr;
- touch->objectID = theObject->objectID;
- touch->propertyID = theValueHdr->theProperty->propertyID;
- touch->typeID = theValueHdr->typeID;
- touch->touchFlags = 0;
- touch->removedEntry = NULL;
-
- cmAddObjToTouchedChain(container, theObject); /* make sure obj. is on touched chain */
-
- cmAppendListCell(&theObject->touchedList, touch); /* add touch to the object */
-
- return (touch); /* return ptr to new touch list entry */
- }
-
-
- /*----------------------------------------------------------------*
- | cmDeleteTouchedList - delete entire touched list for an object |
- *----------------------------------------------------------------*
-
- This routine is only used when all TOC data structures are being freed. For each object
- passed, that object's entire touched list (if any) is freed. Since all TOC entities are
- being freed, there is no need to remove the object from the touched chain since the
- objects on the chain will be freed.
-
- Note, that the object's passed are just objects picked up during the freeing of all the
- objects. Therefore the touched chain is not being followed in the context used here.
-
- We pass in the container as a parameter and not derive the container from the object
- because during updating, an object->container is in the target container, but the
- updating container toc contains that object. When the container is closed, the target
- container is closed and disposed first, so when the target container is closed and
- call cmDeleteTouchedList, object->container is no longer valid.
-
- */
-
- void cmDeleteTouchedList(TOCObjectPtr theObject, ContainerPtr container)
- {
- TouchedListEntryPtr nextTouch, touch;
-
- if (theObject->objectFlags & TouchedObject) { /*if object was touched... */
- theObject->objectFlags &= ~TouchedObject; /* ...unmark just to be safe! */
- touch = (TouchedListEntryPtr)cmGetListHead(&theObject->touchedList);
-
- while (touch != NULL) { /* ...loop through entire touch list*/
- nextTouch = (TouchedListEntryPtr)cmGetNextListCell(touch);
- if (touch->theValueHdr)
- touch->theValueHdr->touch = NULL;
- CMfree(cmDeleteListCell(&theObject->touchedList, touch)); /* ...free each entry */
- touch = nextTouch;
- } /* while */
- } /* TouchedObject */
- }
-
-
- /*-------------------------------------------------------------------------------------*
- | simpleValueTouch - generate touched list entry for a value touched in specified way |
- *-------------------------------------------------------------------------------------*
-
- This routine defines a touched list entry for a value. A touched list entry for the object
- "owning" the value is defined. It is the common internal routine to cmTouchEditedValue(),
- cmTouchImmediateValue(), cmTouchBaseType(), and cmTouchSetInfoedValue(). These are all
- "simple" touches in the sense that the touched list entry is or'ed with a flag appropriate
- to the caller. The flag is passed as the parameter. The touched list entry for the
- object "owning" a value is defined if necessary.
-
- The function returns true if everything went ok and false otherwise. This can only fail
- because of an allocation error creating a touched list entry.
- */
-
- static CMBoolean CM_NEAR simpleValueTouch(TOCValueHdrPtr theValueHdr, CM_USHORT flag)
- {
- ContainerPtr container = theValueHdr->container->updatingContainer;
- TouchedListEntryPtr touch;
-
- /* Don't create touched list entries if we're not updating or we're processing a new */
- /* value header... */
-
- if (!TouchIt(container, theValueHdr->container))
- return (true);
-
- /* If the value has never been touched, create a "modified" touched list entry for it */
-
- touch = theValueHdr->touch; /* not NULL if previously touched */
-
- if (touch == NULL) { /* if this is the first touch... */
- touch = createTouchedListEntry(theValueHdr); /* ...create "modified" entry */
- if (touch == NULL) return (false);
- }
-
- touch->touchFlags |= flag; /* OR in the appropriate flag */
-
- return (true); /* return success */
- }
-
-
- /*------------------------------------------------------------------------*
- | cmTouchEditedValue - generate a touched list entry for an edited value |
- *------------------------------------------------------------------------*
-
- This routine defines a touched list entry for a value when it is edited. A touched list
- entry for the object "owning" the value is defined. The parameter specifies the value
- header being edited. The function returns true if everything went ok and false otherwise.
- This can only fail because of an allocation error creating a touched list entry.
- */
-
- CMBoolean cmTouchEditedValue(TOCValueHdrPtr theValueHdr)
- {
- return (simpleValueTouch(theValueHdr, TouchedEdited)); /* touch as "edited" */
- }
-
-
- /*--------------------------------------------------------------------------------------*
- | cmTouchImmediateValue - generate a touched list entry for a modified immediate value |
- *--------------------------------------------------------------------------------------*
-
- This routine defines a touched list entry for an immediate value when it is edited. A
- touched list entry for the object "owning" the value is defined. The parameter specifies
- the value header of the immediate value being edited. The function returns true if
- everything went ok and false otherwise. This can only fail because of an allocation error
- creating a touched list entry.
- */
-
- CMBoolean cmTouchImmediateValue(TOCValueHdrPtr theValueHdr)
- {
- return (simpleValueTouch(theValueHdr, TouchedImmediate)); /* touch as "immediate" */
- }
-
-
- /*--------------------------------------------------------------------------*
- | cmTouchBaseType - generate a touched list entry for a modified base type |
- *--------------------------------------------------------------------------*
-
- This routine defines a touched list entry for an base type when it is changed. A touched
- list entry for the object "owning" the base type (value) is defined. The parameter
- specifies the value header of the base type value being changed. The function returns
- true if everything went ok and false otherwise. This can only fail because of an
- allocation error creating a touched list entry.
- */
-
- CMBoolean cmTouchBaseType(TOCValueHdrPtr theValueHdr)
- {
- return (simpleValueTouch(theValueHdr, TouchedBaseType)); /* touch as "base type" */
- }
-
-
- /*--------------------------------------------------------------------------------*
- | cmTouchSetInfoedValue - generate a touched list entry for a "set-infoed" value |
- *--------------------------------------------------------------------------------*
-
- This routine defines a touched list entry for an value when its type or generation number
- is changed (i.e., it's "set-infoed"). A touched list entry for the object "owning" the
- value is defined. The parameter specifies the value header of the value being set-infoed.
- The function returns true if everything went ok and false otherwise. This can only fail
- because of an allocation error creating a touched list entry.
-
- Warning: The call to this routine MUST be done before any changes to the type because the
- touch list info is in terms of the original object, property, and type.
- */
-
- CMBoolean cmTouchSetInfoedValue(TOCValueHdrPtr theValueHdr)
- {
- return (simpleValueTouch(theValueHdr, TouchedSetinfoed)); /* touch as "set-infoed" */
- }
-
-
- /*----------------------------------------------------------------------------*
- | cmTouchMovedValue - generate touched lists for a value that is being moved |
- *----------------------------------------------------------------------------*
-
- This routine defines touched list entries for a value move when a CMMoveValue() is done.
- Touched lists entries for the "from" and "to" (source and destination) objects involved
- in the moved value are defined. The parameters specify the value header to be moved
- (theFromValueHdr), the source ("from") TOCObject (theFromObject), the destination ("to")
- TOCObject (theToObject), and destination property ID (toPropertyID). The function
- returns true if everything went ok and false otherwise. This can only fail because of an
- allocation error creating a touched list entry.
-
- Warning: The call to this routine MUST be done before the value is moved because the
- touch list info is in terms of the original object, property, and type. The
- move will obviously change the object and/or property.
-
- Note, that a move only moves the value header passed in here. The segments "go along for
- the ride". By moving the value header, the user's refNum to it remains valid.
-
- The source and destination object's touched lists are modified "appropriately". The
- algorithm is sufficiently complex as to warrent outlining here. Indeed, it is
- sufficiently complex, that the only way to describe it is as a "finite state machine".
- The following is a "state-transition" table describing the FSM. Read states on the left,
- conditions across the top. The intersections are in the form "X/s". This says, "execute
- action X and go to state number s". The special action "NOP" means just go to the
- indicated state.
-
- CMMoveValue(from current location to ...)
- "State"
- Current Location |Orig O,Orig P|Orig O,Diff P|Same O,Diff P| Diff. O |
- =================================|=============|=============|=============|=============|
- Initial state 0 | NOP/0 | A/1 | A/1 | B/2 |
- Orig. Obj., different Property 1 | C/0 | NOP/1 | NOP/1 | D/2 |
- Different 0bject 2 | E/0 | F/1 | NOP/2 | G/2 |
- ==========================================================================================
-
- Note, in this table, "O" and "P" stand for object and property respectively. Also,
- "Orig O" and "Same O" are identical while in states 0 or 1. Here are the actions:
-
- A: if (no "from" touched list entry) create it
- flag "from" touched list entry as "inserted"
-
- B: if (no "from" touched list entry) create it
- flag "from" touched list entry as "removed"
- create "to" touched list entry as "inserted" (set value hdr to point to this)
- set back ptr in "to" "inserted" entry to point to "from" "removed" entry
-
- C: remove "inserted" flag from "from" touched list entry ("from" == "to" object)
- if (touched value not edited or set-infoed)
- delete "from" touched list entry
-
- D: move "from" touched list ("inserted") entry to "to" object
- create "from" touched list entry as "removed"
- set back ptr in "to" "inserted" entry to point to "from" "removed" entry
-
- E: move "from" touched list ("inserted") entry to "to" (original) object
- delete "removed" entry in "to" touched list
- remove "inserted" flag from "from" touched list entry
- if (touched value not edited or set-infoed)
- delete "from" touched list entry
-
- F: move "from" touched list ("inserted") entry to "to" (original) object
- delete "to" "removed" touched list entry
-
- G: move "from" touched ("inserted") list entry to "to" object
-
- State 0 occurs when an object has never moved (or was moved back to it's original
- position). This is the initial state. There could still be a touched list entry for
- the value if it was edited or set-infoed.
-
- State 1 is used when an value is moved to or within the same original object. This
- can be from another object (moved there from the original object by an earlier move)
- within the original object, but from a different property (again from an earlier move).
-
- State 2 is entered when a value is moved to a different object.
-
- The general "gist" of this whole thing is to generate a "removed" entry at the original
- source and an "inserted" at the destination. The "removed" entry is suppressed when
- the "inserted" is for the same original object. Both "removed" and "inserted" are
- suppressed when a value is moved back to its original object and property.
-
- Note, an actual state number is not maintained in the touched list entries. Instead we
- infer the state by current conditions.
- */
-
- CMBoolean cmTouchMovedValue(TOCValueHdrPtr theFromValueHdr, TOCObjectPtr theFromObject,
- TOCObjectPtr theToObject, CMObjectID toPropertyID)
- {
- ContainerPtr container = theFromValueHdr->container->updatingContainer;
- TouchedListEntryPtr fromTouch, toTouch;
- CMBoolean moveWithinSameObject;
-
- /* Don't create touched list entries if we're not updating or we're processing a new */
- /* value header... */
-
- if (!TouchIt(container, theFromValueHdr->container))
- return (true);
-
- /* We can tell if we're in the initial state (0) by whether the value was ever */
- /* previously touched. If it wasn't we know it's the first touch. If it was, then */
- /* if the touched list entry is not flagged as "inserted" we still have the initial */
- /* move state. It is touched because it was edited or set-infoed. */
-
- /* Actions "A" and "B" occur here; a "from" touched list entry is created and flagged */
- /* as "inserted" or "removed". If "removed", a "to" "inserted" touch list entry is */
- /* created. */
-
- fromTouch = theFromValueHdr->touch; /* not NULL if already touched*/
- moveWithinSameObject = (CMBoolean)(theFromObject==theToObject);/*moving within same obj*/
-
- if (fromTouch == NULL || (fromTouch->touchFlags & TouchedInserted) == 0) {
- if (fromTouch == NULL) { /* create "from" list entry */
- fromTouch = createTouchedListEntry(theFromValueHdr);
- if (fromTouch == NULL) return (false);
- }
-
- if (moveWithinSameObject) /* "Orig O,Diff P"... */
- fromTouch->touchFlags |= TouchedInserted; /* ...action "A" */
- else { /* "Diff. O"... */
- if ((toTouch = (TouchedListEntryPtr)CMmalloc(sizeof(TouchedListEntry))) == NULL) {
- Container_Disable(container);
- ERROR1(CM_err_NoTouchedEntry, CONTAINERNAME);
- return (false);
- }
- *toTouch = *fromTouch; /* create "to" list entry */
- cmAppendListCell(&theToObject->touchedList, toTouch);
-
- cmAddObjToTouchedChain(container, theToObject); /* put "to" obj. on touched chain */
-
- toTouch->touchFlags |= TouchedInserted; /* flag "to" entry as "inserted" */
- fromTouch->touchFlags = TouchedRemoved; /* flag "from" as "removed" */
- toTouch->removedEntry = fromTouch; /* set back ptr to "from" entry */
- theFromValueHdr->touch = toTouch; /* point value hdr at "inserted" */
- fromTouch->removedEntry = NULL; /* make sure of "from" back ptr */
- }
-
- return (true); /* end of actions "A"/"B"; state 0*/
- } /* 1st move */
-
- /* At this point only states 1 and 2 are possible, i.e., the object has been moved. */
- /* Unlike the actions listed in the comments some common parts can be factored out. */
- /* The first thing to factor is the moving of the "inserted" entry to the "to" object */
- /* for actions "D", "E", "F", and "G". It is unnecessary to move the "inserted" entry */
- /* if we're moving within the same object. The "inserted" entry will still point to */
- /* the moved value header whether moved or not moved. */
-
- if (!moveWithinSameObject) { /* factor "D", "E", "F", "G"... */
- cmDeleteListCell(&theFromObject->touchedList, fromTouch); /* move "inserted" to "to"*/
- cmAppendListCell(&theToObject->touchedList, fromTouch);
- cmAddObjToTouchedChain(container, theToObject); /* put "to" obj. on touched chain */
- }
-
- /* At this point the "inserted" entry is considered moved to the "to" object. We now */
- /* have to see if we're in state 2 or 3. It will be 3 if the "to" object ID is */
- /* different from the original object ID of the value which was put in the value's */
- /* touched list entry when it was first created. */
-
- /* For moving to a different object, we must check that the "from" is in the original */
- /* object. If it is, there is no "removed" touched list entry. We therefore must */
- /* create it at set the back pointer of the "to" "inserted" entry to point to it. */
-
- toTouch = fromTouch; /* "to" now has "inserted" entry */
-
- if (toTouch->objectID != theToObject->objectID) { /* "Diff. O" ==> "D" or "G" */
- if (toTouch->removedEntry == NULL) { /* coming from state 0 ("B")==>"D"*/
- if ((fromTouch = (TouchedListEntryPtr)CMmalloc(sizeof(TouchedListEntry))) == NULL) {
- Container_Disable(container);
- ERROR1(CM_err_NoTouchedEntry, CONTAINERNAME);
- return (false);
- }
- *fromTouch = *toTouch; /* create "from" list entry */
- cmAppendListCell(&theFromObject->touchedList, fromTouch);
-
- fromTouch->touchFlags = TouchedRemoved; /* flag "from" as "removed" */
- toTouch->removedEntry = fromTouch; /* set back ptr to "from" entry */
- fromTouch->removedEntry = NULL; /* no back ptr in "removed" entry */
- }
-
- return (true); /* end of actions "D"/"G" */
- }
-
- /* States 2 and 3 are still in effect here. But now we also know that we're moving */
- /* the value back to the original object or we're already there. Thus actions "C", */
- /* "E", and "F" are possible. So are the NOP actions. For action "F" there is a */
- /* back pointer to the "removed" entry. There is no "removed" entry when there is an */
- /* "inserted" in the original object. Thus it is deleted first... */
-
- if (toTouch->removedEntry != NULL) { /* action "F" ==> delete "removed"*/
- CMfree(cmDeleteListCell(&theToObject->touchedList, toTouch->removedEntry));
- toTouch->removedEntry = NULL; /* no back ptr any more */
- }
-
- /* If we're moving back to the original object and property, then put things back to */
- /* the initial state (action "E"). The "inserted" entry is all that remains in the */
- /* "to" object. Thus we play around with it. If we're not moving back to the */
- /* original property, then we've completed actions "C" and "F" and there is nothing */
- /* else to do. Note that the "initial state" may not be quite as it was when the */
- /* value was moved that first time. It could have been edited along the way. It */
- /* could also have been edited before the initial move. Either way, if that is the */
- /* case, we must keep a touched list entry. */
-
- if (toTouch->propertyID == toPropertyID) { /* moving back to orig. property */
- toTouch->touchFlags &= ~TouchedInserted; /* remove "inserted" flag */
- toTouch->removedEntry = NULL; /* make sure of back ptr */
-
- if ((toTouch->touchFlags & TouchedModified) == 0) { /* keep entry if */
- CMfree(cmDeleteListCell(&theToObject->touchedList, toTouch)); /* other things done*/
- theFromValueHdr->touch = NULL; /* to touched value */
- }
- }
-
- return (true); /* return success */
- }
-
-
- /*---------------------------------------------------------------------------------------*
- | cmTouchDeletedValue - generate a touched list entry for a value that is being deleted |
- *---------------------------------------------------------------------------------------*
-
- This routine defines a touched list entry for a value when an explicit CMDeleteValue() is
- done. A touched list entry for the object "owning" a value that is (to be) deleted is
- defined. The parameters specify the value header to be deleted (theValueHdr) and the
- value's TOCObject (theObject). The function returns true if everything went ok and false
- otherwise. This can only fail because of an allocation error creating a touched list
- entry.
-
- Values are deleted individually and explicitly via CMDeleteValue(), or implicitly by
- implicit deletion of the value's property. An implicit property deletion comes about
- when all the values of a property are explicitly deleted or moved out, or if the object
- itself is deleted. Implicit value deletions are handled cmImplicitDeleteValueTouch()
- specially as descriped there.
-
- For explicit deletions, touched list entry for a deleted value is always created or set
- to "deleted value" in the ORIGINAL object. If the value has been moved to a different
- object, then the "inserted" entry for the value (there must be one) has a back pointer to
- the original touched list entry entry which must be flagged "removed". That "removed"
- entry is therefore changed to "deleted value" and the "insert" entry is removed.
- */
-
- CMBoolean cmTouchDeletedValue(TOCValueHdrPtr theValueHdr, TOCObjectPtr theObject)
- {
- ContainerPtr container = theValueHdr->container->updatingContainer;
- TouchedListEntryPtr touch;
-
- /* Don't create touched list entries if we're not updating or we're processing a new */
- /* value header... */
-
- if (!TouchIt(container, theValueHdr->container))
- return (true);
-
- /* If we are deleting last value so that property will be deleted, record this as */
- /* delete property, this is because the property may be freed, so we will not know */
- /* the property id if we record it as delete value */
-
- if (theValueHdr->theProperty->valueHdrList.nbrOfCells == 1)
- return cmTouchDeletedProperty(theValueHdr->theProperty, theObject);
-
- /* If the value has never been touched, create a "deleted value" touched list entry */
- /* for it... */
-
- touch = theValueHdr->touch; /* not NULL if previously touched */
-
- if (touch == NULL) { /* if this is the first touch... */
- touch = createTouchedListEntry(theValueHdr); /* ...create "deleted value" entry*/
- if (touch == NULL) return (false);
- }
-
- /* At this point the value has a corresponding touched list entry. If it is the first */
- /* touch, or touched but not moved, or moved, but it's in the original object (i.e., */
- /* there's no "removed" touched list entry), then change the existing touched list */
- /* entry to "deleted value". Then we're done. */
-
- if ((touch->touchFlags & TouchedInserted) == 0 || touch->removedEntry == NULL) {
- touch->touchFlags = TouchedDeletedValue; /* changed to "deleted value" */
- return (true); /* exit successfully */
- }
-
- /* If the value has been moved to a different object, the back pointer in the */
- /* corresponding touched list ("inserted") entry points to a "removed" entry in the */
- /* original object. Change the "removed" entry to "deleted value" and delete the */
- /* "inserted" entry where ever the value happens to be at this moment. */
-
- touch->removedEntry->touchFlags = TouchedDeletedValue; /* "removed"=>"deleted value"*/
- CMfree(cmDeleteListCell(&theObject->touchedList, touch));/* delete "inserted" entry */
-
- return (true); /* return success */
- }
-
-
- /*----------------------------------------------------------------------------------------*
- | cmImplicitDeleteValueTouch - delete touched list entry for an implicitly deleted value |
- *----------------------------------------------------------------------------------------*
-
- This routine is called whenever a value is IMPLICITLY deleted because an object or a
- property is deleted. In that case, all the values for the object, or one of the object's
- property's are deleted. We also must delete the the corresponding touched list entry
- if there is one for those values since there isn't going to be any value there. However,
- if a value was moved in from another object, then the "removed" entry in the original
- value is flagged as "deleted value".
-
- Implicit deletion of values because their property is going away is essentially a subset
- of the explicit deletion case handled by cmTouchDeletedValue(). The difference between
- deleting an individual value, and deleting a value when its property is going to be
- deleted, is that a "deleted value" entry is created in the individual delete case, while
- the entry is always deleted in the property value delete case. What both have in common,
- however, is that "removed" entries in other objects for values that were moved in to the
- property being deleted are set to "deleted value".
-
- The current callers to this routine are (both in TOCEnts.c ):
-
- cmDeleteProperty() Called when a CMDeleteObjectProperty() is done. A single property
- is deleted. Hence all the values for that property are implicitly
- deleted.
-
- deleteProperties() Internal routine called when an object is deleted. There, all the
- properties of the object are to be deleted. This is action routine
- for cmDelete1Object(). cmDelete1Object() is called whenever all the
- properties are deleted by deleting values or deleting properties.
- In those cases however, values would have been singly deleted and
- cmTouchDeletedValue() would be handling those. Only when the
- deletion is a byproduct does this routine get called.
- */
-
- void cmImplicitDeleteValueTouch(TOCValueHdrPtr theValueHdr, TOCObjectPtr theObject)
- {
- ContainerPtr container;
- TouchedListEntryPtr touch = theValueHdr->touch;
-
- if (touch != NULL) { /* if touched value... */
- if (touch->touchFlags & TouchedInserted) /* ...if "inserted"... */
- if (touch->removedEntry) /* ...and has "removed" entry */
- touch->removedEntry->touchFlags = TouchedDeletedValue;/* make a "deleted value" */
-
- container = theObject->container; /* needed for CMfree() */
- CMfree(cmDeleteListCell(&theObject->touchedList, touch));/* delete touched entry */
- theValueHdr->touch = NULL; /* fix hdr accordingly */
- }
- }
-
-
- /*-------------------------------------------------------------------------------*
- | cmTouchDeletedProperty - generate a touched list entry for a deleted property |
- *-------------------------------------------------------------------------------*
-
- This routine defines a touched list entry for a property when the property is explicitly
- deleted by CMDeleteObjectProperty(). A touched list entry for the object "owning" the
- property that has been deleted is defined. The parameters specify the property of the
- object to be deleted (theProperty) and the property's TOCObject (theObject). The function
- returns true if everything went ok and false otherwise. This can only fail because of an
- allocation error creating a touched list entry.
-
- Note, this routine is slightly different from touches on values in that no value is
- involved explicitly in the property delete. They are, however, implicitly involved to
- the degree that all values are deleted from the property (and the value headers put on
- the deleted values list). The process of value deletion deletes all the touched list
- entries for those values in the property's object. Any values moved into this object
- from other objects also have a "removed" touched list entry in the original object.
- Deleting such values for the property has exactly the same effect as CMDeleteValue(),
- i.e., the "removed" entry is changed to a "deleted value" entry. See
- cmTouchDeletedValue() for further details.
-
- Note, that values previously moved OUT of the object containing the property being deleted
- have "removed" entries of their own in this object. Thus, there still could be "dangling"
- "removed" entries on this object's touched list; dangling, because no value header of the
- property being deleted points to it -- the value header has been moved out! These must
- remain in the normal course of move handling. However, see what happens to these things
- when the entire object is deleted! Read cmTouchDeletedObject() for details.
-
- Because a deleted property must create a touched list entry, we must do it here, because
- the standard touched list creation routine (createTouchedListEntry()) deals only with
- values (headers).
- */
-
- CMBoolean cmTouchDeletedProperty(TOCPropertyPtr theProperty, TOCObjectPtr theObject)
- {
- ContainerPtr container = theObject->container->updatingContainer;
- TouchedListEntryPtr touch;
- TOCValueHdrPtr theValueHdr;
-
- /* Don't create touched list entries if we're not updating or we're processing a new */
- /* object... */
-
- if (!TouchIt(container, theObject->container))
- return (true);
-
- /* Create the "deleted property" touched list entry... */
-
- if ((touch = (TouchedListEntryPtr)CMmalloc(sizeof(TouchedListEntry))) == NULL) {
- Container_Disable(container);
- ERROR1(CM_err_NoTouchedEntry, CONTAINERNAME);
- return (false);
- }
-
- cmNullListLinks(touch); /* init entry fields... */
- touch->theValueHdr = NULL;
- touch->objectID = theObject->objectID;
- touch->propertyID = theProperty->propertyID;
- touch->typeID = 0;
-
- /* When a property is deleted, values are also deleted. If all values are from a */
- /* container that will later be merged, then we don't need to generate the delete */
- /* update instruction later. So we look through this to see if there is at least one */
- /* value from a container that is not a merge candidate */
- theValueHdr = (TOCValueHdrPtr)cmGetListHead(&theProperty->valueHdrList);
- while (theValueHdr) {
- if (!theValueHdr->container->updateMergeCandidate) {
- touch->typeID = theValueHdr->typeID;
- break;
- }
- theValueHdr = (TOCValueHdrPtr)cmGetNextListCell(theValueHdr);
- }
-
- touch->touchFlags = TouchedDeletedProperty; /* "deleted property" entry */
- touch->removedEntry = NULL;
-
- cmAddObjToTouchedChain(container, theObject); /* put obj. is on touched chain */
-
- cmAppendListCell(&theObject->touchedList, touch); /* add touch to the object */
-
- return (true); /* return success */
- }
-
-
- /*---------------------------------------------------------------------------*
- | cmTouchDeletedObject - generate a touched list entry for a deleted object |
- *---------------------------------------------------------------------------*
-
- This routine is called whenever an object is deleted. The object is placed on the touched
- chain if it is not already there. The object's touched chain, if any, is deleted EXCEPT
- for "removed" entries for values previously moved out of this object.
-
- The "removed" entries are kept because there are references to these entries from
- "inserted" entries from the other objects (touched list) to where the values were moved.
- If a moved value is itself deleted after the object it came from is deleted, then the back
- pointer in its "inserted" entry must be kept valid so that the "removed" entry it points
- to can be changed to a "deleted value". Although this "deleted value" is on a touched
- list of a deleted object, it won't cause harm, since the final walks of the touched chain
- know to expect this.
-
- Deleted objects are moved to the deleted objects list and flagged as deleted. This is
- done to protect against the user reusing the refNum after the delete. Thus an object on
- the touched chain, but flagged as deleted, is enough to indicated to close-time
- processing what is going on. The remaining touched list "goes along for the ride".
-
- This routine MUST be called AFTER all objects and properties for the object have been
- deleted. By that time all touched list entries for values will have been processed
- (using cmImplicitDeleteValueTouch()). The reason it must be after is that if it were
- before, we could have touched value headers pointing to touched list entries we free up
- here. Not too cool! cmImplicitDeleteValueTouch() would have a hard time dealing with
- that.
- */
-
- void cmTouchDeletedObject(TOCObjectPtr theObject)
- {
- ContainerPtr container = theObject->container->updatingContainer;
- TouchedListEntryPtr touch, nextTouch;
-
- /* Don't create touched list entries if we're not updating or we're processing a new */
- /* object... */
-
- if (!TouchIt(container, theObject->container))
- return;
-
- /* Look at the remaining touched list entries for the deleted object (if any). By */
- /* point there should only be "removed" entries for values previously moved out of */
- /* this object. These must remain so that if a moved value is itself deleted, its */
- /* "inserted" entry back pointer has a valid "removed" entry to point at! Everything */
- /* else that might be left on the touched list is freed. There should be anything, */
- /* but what the hell. Continuing in this "paranoid" mode, if we see any "inserted" */
- /* entries, we use thier back pointers to change their corresponding "removed" entries*/
- /* to "deleted value". */
-
- touch = (TouchedListEntryPtr)cmGetListHead(&theObject->touchedList);
-
- while (touch) { /* process touched list */
- nextTouch = (TouchedListEntryPtr)cmGetNextListCell(touch); /* get next touch entry */
-
- if (touch->touchFlags & TouchedInserted) /* ...if "inserted"... */
- if (touch->removedEntry) /* ...+ "removed" entry */
- touch->removedEntry->touchFlags = TouchedDeletedValue; /* make "deleted value" */
-
- if ((touch->touchFlags & TouchedRemoved) == 0) { /* if not "removed"... */
- if (touch->theValueHdr)
- touch->theValueHdr->touch = NULL;
- CMfree(cmDeleteListCell(&theObject->touchedList, touch)); /* delete touched entry */
- }
- touch = nextTouch; /* process next entry */
- } /* while */
-
- /* Make sure object is on the touched chain... */
-
- cmAddObjToTouchedChain(container, theObject);
- }
-
-
- /*---------------------------------------------------------------------------------*
- | generateDataEdits - generate insert/delete (data editing) updating instructions |
- *---------------------------------------------------------------------------------*
-
- This routine is called only by genValueUpdateInstructions() to generate data edits, i.e.
- data insert and delete sequences, for a value. The value header and the owning object's
- special "updates" property value refNum are passed.
-
- The value refNum is where the updating instructions are written. The value header is
- used to analyze the value segments to see what has to be done. The fact that this
- routine is called at all means that this value has been touched because it was written
- to or had some of its data deleted. We also know that the value is not immediate since
- they are handled separately using a distinct touch (flag). Further, by this point, the
- value header ptr is not NULL and has at least one segment. All of these checks were
- done.
-
- In order to understand the algorithm used here to determine data inserts and deletes, the
- "logical offset" and "logical size" for a value must be described and what is expected in
- the segment arrangements. Assume the following example value header and sequence of
- value segments:
-
- *-------------* X Y
- | Value Hdr | *-------------* *-------------* *-------------* *-------------*
- | |-->| Value Seg. |-->| Value Seg. |-->| Value Seg. |-->| Value Seg. |
- |- - - - - - -| |- - - - - - -| |- - - - - - -| |- - - - - - -| |- - - - - - -|
- | | | "log. off." | | (new) | | (new) | | "log. off." |
- | "log. size" | *-------------* *-------------* *-------------* *-------------*
- *-------------* |<---- l ---->|
-
- The "logical size" is the original total size of the data as it was read in at open time.
- It remains constant in the value header. The "logical offset" is the original start of
- each value segment when the TOC was loaded at open time. This is the relative offset of
- the start of each value segment, viewing each segment as the user would, i.e., as
- contiguous data. This offset has nothing to do with the actual data offset in the
- container.
-
- As each segment is created, it is "tagged" to its "owning" container. This tag takes the
- form of a pointer to the container (control block) that owns it. "New" segments are
- therfore created after open time and will always belong to the updating container. "Old"
- segments are any segments not belonging to the updating container.
-
- Therfore, a data insert can always be determined simply by seeing if the segment is a
- "new" segment. In the above example, two "new" segments appear between two "old"
- segments.
-
- Of course, "new" segments" never have a valid logical offset given to them. The logical
- offsets are only set at open time in the "old" segments. They supply the information
- needed to determine a delete and also where new segments are inserted.
-
- When data is deleted from an old segment, its logical offset is "corrected" as necessary.
- For example, if a segment started at offset X, and the n left-most bytes are deleted from
- that segment, then its corrected logical offset is X+n. Corrections are done for all
- the delete possibilites on old segments as appropriate.
-
- Thus, knowing that ORIGINALLY the logical offsets in each old segment were contiguous
- (i.e., the logical offset for a segment plus that segments length exactly equals the
- logical offset of the next old segment), a deletion is detected when the starting offset
- is different than what's expected for it. The "expected" logical offset of the next old
- segment is always the offset of current segment plus the current segment's length. The
- original contiguous set of segments always has the "expected" offsets.
-
- Deletions can only produce a corrected logical offset that is greater than what is
- expected. The difference of the corrected logical offset and what is expected is the
- number of bytes deleted. The starting point for the deleted is just the expected logical
- offset.
-
- Note, for insertions, the insertion point is also the expected logical offset. If the
- next segment after an old segment is a new segment, then it must be at the expected
- offset. This is true even if an old segment had to be split to do the insertion. The
- logical offsets in the split old segments are appropriately corrected to be "contiguous"
- (from a logical offset point of view).
-
- All through the above discussion the point was made that a deletion is determined using
- the expected logical offset and the logical offset of the "next old" segment. The
- emphasis is on the "next old", because it doesn't matter how many new segments may be
- inserted between the two old segments. This is illustrated above. Thus both a delete
- and insert can be detected and produced during analysis. Indeed, overwrites are
- produced when updating as deletes followed by inserts.
-
- So, in the above example, the first old segment starts at offset X (in reality, the first
- segment will always be 0, but generalize this to an arbitrary sequence of segments that
- may not be first). It's length is l. The expected logical offset for the next old
- segment is X+l. The next old segment starts at logical offset Y. Thus if Y > (X+l) then
- (X+l)-Y bytes have been deleted. Also, two new segments are inserted at X+l.
-
- The algorithm below always assumes we start with an old segment and looks for the next
- old segment, keeping track of new segments in between. The expected offset is maintained.
- The deletes and/or inserts are generated whenever we don't get what's expected or we
- encountered new segments. That's basically all there is to it!
-
- One final point. Nothing was mentioned above about how the "logical size" is used.
- Essentially, it just modifies this algorithm by always pretending there is a final old
- segment whose logical offset is the logical size. This is needed when checking for
- deletions of the last n bytes of the data.
-
- Finally, the insert/delete updating instructions generated have the following formats:
-
- +---+---+---+---+---+---+---+---+---+
- DeleteData | |starting offset| amount |
- +---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
- InsertData1 | | insert point | data offset | data length |
- (N = 1) +---+---+---+---+---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
- InsertDataN | | insert point | N | data offset1 | data length1 |
- (N > 1) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
- - - -
- +---+---+---+---+---+---+---+---+
- | data offsetN | data lengthN |
- +---+---+---+---+---+---+---+---+
-
- There are two data insert formats; one when only one segment is to be inserted, and one
- when N > 1.
- */
-
- static void CM_NEAR generateDataEdits(TOCValueHdrPtr theValueHdr, void *ioBuffer)
- {
- ContainerPtr container = theValueHdr->container->updatingContainer;
- TOCValuePtr theValue, theNewValue;
- CM_ULONG expected, nextOldLogicalOffset, nbrOfInsertedValues;
- long adjustment, deleteSize, insertSize;
-
- /* To make this code a little easire to follow, the following macros are defined... */
-
- #define NewValue(v) ((v)->container == container) /* check for an "new" value seg. */
- #define OldValue(v) ((v)->container != container) /* check for an "old" value seg. */
-
- #define LogicalOffset(v) ((v)->logicalOffset) /* logical offset of an old seg. */
- #define LogicalSize(vh) ((vh)->logicalSize) /* logical size */
-
- /* Process all the segments. The variable "expected" is maintained as the expected */
- /* offset to the next segment. */
-
- expected = 0; /* 0 is initial expected offset */
- adjustment = 0; /* no "adjustment" yet */
-
- for (theValue = (TOCValuePtr)cmGetListHead(&theValueHdr->valueList);;) {
-
- /* Skip contiguous old segments, i.e., old segments that have the expected offsets. */
-
- while (theValue != NULL && /* skip while there are segments */
- OldValue(theValue) && /* ...and its "old" */
- LogicalOffset(theValue) == expected) { /* ...and is contiguous... */
- expected = LogicalOffset(theValue) + theValue->value.notImm.valueLen;
- theValue = (TOCValuePtr)cmGetNextListCell(theValue);
- }
-
- /* A new segment or an old segment with a logical offset that was not expected has */
- /* been seen (or there are no more segments). Remember this segment assuming it's */
- /* new segment and skip over the new segments if it indeed is a neew segment. A */
- /* count of the number of new segments is done to indicate later that we do have an */
- /* insertion and how many segments. This will also iindicate which insertion */
- /* control code to generate (InsertData1 or InsertDataN). */
-
- theNewValue = theValue; /* remember where the segment is */
- nbrOfInsertedValues = 0; /* assume it's not a new segment */
- insertSize = 0; /* accumulate size of this insert */
-
- while (theValue != NULL && NewValue(theValue)) { /* count nbr of new inserted segs */
- insertSize += theValue->value.notImm.valueLen; /* ...and size of this insertion */
- ++nbrOfInsertedValues;
- theValue = (TOCValuePtr)cmGetNextListCell(theValue);
- }
-
- /* The current segment is always an old segment here (assuming "old" if there is no */
- /* more segments). This old segment is either the one immediately following a set */
- /* of one or more newly inserted segments, or it's just then next old segment that */
- /* doesn't have the expected logical offset. Either way, we process deletions here.*/
-
- if (theValue == NULL || OldValue(theValue)) { /* have next old segment... */
- nextOldLogicalOffset = (theValue != NULL) ? LogicalOffset(theValue) :
- LogicalSize(theValueHdr);
- if (expected < nextOldLogicalOffset) { /* generate deletion if required */
- deleteSize = nextOldLogicalOffset - expected;
- PUT1(DeleteData , ioBuffer); /* <DeleteData> */
- PUT4(expected+adjustment, ioBuffer); /* offset */
- PUT4(deleteSize , ioBuffer); /* amount */
- } else
- deleteSize = 0;
- } /* deletions */
-
- /* If new segments were encountered, generate the insertion instructions... */
-
- if (nbrOfInsertedValues > 0) { /* if N inserted segments (N > 0) */
- if (nbrOfInsertedValues == 1) { /* N = 1... */
- PUT1(InsertData1 , ioBuffer); /* <InsertData1> */
- PUT4(expected+adjustment, ioBuffer); /* insertPt */
- } else { /* N > 1... */
- PUT1(InsertDataN , ioBuffer); /* <InsertDataN> */
- PUT4(expected+adjustment, ioBuffer); /* insertPt */
- PUT4(nbrOfInsertedValues, ioBuffer); /* N */
- }
-
- while (nbrOfInsertedValues--) { /* generate N offset/length pairs */
- PUT4(theNewValue->value.notImm.value , ioBuffer);
- PUT4(theNewValue->value.notImm.valueLen, ioBuffer);
- theNewValue = (TOCValuePtr)cmGetNextListCell(theNewValue);
- }
- } /* insertions */
-
- /* Exit if there are no more segments. If there are, reset the expected logical */
- /* offset to the (corrected) value in the current segment. Processing continues */
- /* using this segment. Thus the end of one interation around this loop overlaps */
- /* next, with the "end" old segment of the previous interation being the "start" */
- /* segment of the next iteration. */
-
- if (theValue == NULL) break; /* done if no more segments */
- expected = LogicalOffset(theValue); /* reset expected logical offset */
- adjustment += (insertSize - deleteSize); /* fix "adjustment" to expected */
- } /* for */
- }
-
-
- /*-------------------------------------------------------------------*
- | genValueUpdateInstructions - generate value updating instructions |
- *-------------------------------------------------------------------*
-
- This routine is called only by generateValueUpdates() to generate the specific value
- updating instruction sequences needed to bring a value "up-to-date". The touched list
- entry for the value and the owning object's special "updates" property value refNum are
- passed.
-
- The value refNum is where the updating instructions are written. The touch list entry
- pointer indicates what has been done to the value and its original "OPT" address. The
- entry also points to the touched value header (except for "removed" values). The value
- header's flags, in turn, can add additional information about what's been done to the
- value. Specifically, whether the value's been edited, set-infoed, immediate, and so on.
-
- By the time generateValueUpdates() calls this routine all the updating instructions
- sequences needed to address the value in question have been output. A "<new prop> P T"
- sequence when the property changes, or a "<new type> T" sequence when a new value is
- to be processed but for the "current" same property. The object is never needed since
- these sequences are attached to the object in its special "updates" property.
-
- After generateValueUpdates() generates the "<new prop> P T" or "<new type> T", it calls
- this routine to generate "<v> [params...]", where <v> is a control code (byte) and
- [params...] is one or more parameters as a function of that control code. The following
- diagrams show the exact layout of the sequences generated for each value control code:
-
- +---+
- RemovedValue | |
- +---+
-
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
- InsertedValue | | original O | original P | original T |
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+
- SetInfoedValue | | new T | geneneration |
- +---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+
- DeleteData | |starting offset| amount |
- +---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
- InsertData1 | | insert point | data offset | data length |
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
- InsertDataN | | insert point | N | data offset1 | data length1 |
- +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
- - - -
- +---+---+---+---+---+---+---+---+
- | data offsetN | data lengthN |
- +---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+
- ReplaceImmediate | | l | new imm value |
- (length=l<=4) +---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+ +---+---+---+---+
- ReplaceBaseType | |n bases| base type #1 | - - - | base type #n |
- +---+---+---+---+---+---+---+ +---+---+---+---+
-
- +---+
- DeleteValue | |
- +---+
-
- The way to view a complete value update sequence, i.e., "<new prop> P T <v> [params...]"
- or "<new type> T <v> [params...]" is that the "<v> [params...]" act upon their receiver.
- It's sort of like a postfix notation.
-
- Note also that more that one sequence may be generated for a specific value. Thus the
- [params...] definition is recursive and itself can have "<v> [params...]" sequences.
-
- The following code tests each of the value touch flags to generate each flag's associated
- sequence as described above. The tests follow one after another. Some return if
- satisfied, other fall through to the next test. THE TESTING ORDER IS NOT ARBITRARY (except
- for, perhaps, the ones that don't fall through). Here are the tests in the indicated
- order and the reason(s) they are in that order.
-
- 1. "removed" Value removed from this object. Nothing else can be done to it.
- Done first to get it out of the way.
-
- 2. "deleted value" Value deleted from this object. Nothing else can be done to it.
- Also done second to get it out of the way too. It could have
- been done first.
-
- 3. "inserted" Data moved from someplace else. This must be done before anything
- further is done to the value. It has to be moved to this object
- from its original address since this set of update instructions
- is attached to the destination object and property (where the
- value was moved to). The destination property for the object
- need NOT exist prior to the move at open-time. The move brings
- it into existance. Thus "inserted" has to be first so that there
- is a value to operate on by the other updates.
-
- All the following tests deal with a value (header) that remains after updating. These
- tests fall through to one another. Most are mutually exclusive, but the order is more or
- less logically chosen.
-
- 4. "base type changed" Replace base type array of IDs. Base types are basically
- immediate data. Immediates are processed early to get them out
- of the way too. Base types, however, cannot be set-infoed. So
- they are processed as the first value operation.
-
- 5. "set-infoed" Set-infoing can be done in addition to all further sequences so
- it should be done now.
-
- 6. "immediate" Replace immediate with new value. This is the last of the
- immediate value possibilities. A "few words" should be said
- about what happens to immediate data if it needed to be converted
- to non-immediate during editing...
-
- If a value has made it this far and is still an immediate, then
- a "replace immediate" is the most efficient way to deal with it.
- If, along the way, it was converted to a non-immediate, then
- the immediate data is written to the updating container as "new"
- data. From our point of view here it looks as though the value
- data was completely overwritten (i.e., deleted and new data
- inserted). We can't tell the difference, and the delete/insert
- sequences, as done with non-immidate overwrites is generated.
- There is no special case.
-
- 7. "edited" Data insert and delete are the last (and most complicated)
- operations that can be performed on the (non-immediate) data, so
- they are placed last (so I can avoid figuring out how to code
- them as long as possible -- how's that for a reason?).
- */
-
- static void CM_NEAR genValueUpdateInstructions(TouchedListEntryPtr touch, void *ioBuffer)
- {
- TOCValueHdrPtr theValueHdr;
- TOCValuePtr theValue;
- CM_USHORT touchFlags = touch->touchFlags;
-
- /* 1. "removed": <RemovedValue> */
-
- if (touchFlags & TouchedRemoved) { /* 1. "removed" */
- PUT1(RemovedValue, ioBuffer); /* <RemovedValue> */
- return; /* exit */
- }
-
- theValueHdr = touch->theValueHdr; /* point at the touched valueHdr*/
-
- /* 2. "deleted value": <DeleteValue> */
-
- if (touchFlags & TouchedDeletedValue) { /* 2. "deleted value" */
- PUT1(DeleteValue, ioBuffer); /* <DeleteValue> */
- return; /* exit */
- }
-
- /* 3. "inserted": <InsertedValue> O P T */
-
- if (touchFlags & TouchedInserted) { /* 3. "inserted" */
- PUT1(InsertedValue , ioBuffer); /* <InsertedValue> */
- PUT4(touch->objectID , ioBuffer); /* O */
- PUT4(touch->propertyID, ioBuffer); /* P */
- PUT4(touch->typeID , ioBuffer); /* T */
- }
-
- /* The following are all the value data or info manipulations... */
-
- theValue = (TOCValuePtr)cmGetListHead(&theValueHdr->valueList); /* and 1st value */
-
- /* 4: "base type changed": <ReplaceBaseType> N baseType1 . . . baseTypeN */
-
- if (touchFlags & TouchedBaseType) { /* 4: "base type changed" */
- PUT1(ReplaceBaseType, ioBuffer); /* <ReplaceBaseType> */
-
- PUT2(cmCountListCells(&theValueHdr->valueList), ioBuffer);/* N */
-
- while (theValue != NULL) { /* bt1 . . . btN*/
- PUT4(theValue->value.imm.ulongValue, ioBuffer);
- theValue = (TOCValuePtr)cmGetNextListCell(theValue);
- }
- }
-
- /* 5: "set-infoed": <SetInfoedValue> T g */
-
- if (touchFlags & TouchedSetinfoed) { /* 5: "set-infoed" */
- PUT1(SetInfoedValue , ioBuffer); /* <SetInfoedValue> */
- PUT4(theValueHdr->typeID , ioBuffer); /* T */
- PUT4(theValueHdr->generation, ioBuffer); /* g */
- }
-
- /* 6: "immediate": <ReplaceImmediate> length value */
-
- if (touchFlags & TouchedImmediate) { /* 6: "immediate" */
- if (theValue->flags & kCMImmediate) { /* ...only if not converted! */
- PUT1(ReplaceImmediate , ioBuffer); /* <ReplaceImmediate> */
- PUT1(theValue->value.notImm.valueLen, ioBuffer); /* length */
- PUT4Direct(theValue->value.imm.ulongValue, ioBuffer);/* value */
- return;
- }
- }
-
- /* 7: "edited": <DeleteData> offset amount */
- /* <InsertDataX> [N] insertPt offset length [...] */
-
- if (touchFlags & TouchedEdited) /* 7: "edited" */
- generateDataEdits(theValueHdr, ioBuffer); /* generate separately... */
- }
-
-
- /*------------------------------------------------------------------*
- | cmPastUpdateOnTouchList - make sure past update is on touch list |
- *------------------------------------------------------------------*
-
- When we are doing merging of updating containers, what we are doing is to take all
- the updates in the top container, and generate them again as updates in the
- merged containers. However these updates may not have been touched in the latest
- edit. To ensure they will not be omitted in the generate update process, we go
- through all the past updates and put them on the touch list so they will be dealt
- with during the generate update process.
- */
-
- void cmPastUpdateOnTouchList(ContainerPtr container)
- {
- PastUpdate *pastUpdate = cmGetListHead(&container->tmpList);
- void *toc = container->updatingContainer->toc;
- TOCObjectPtr theObject;
-
- while (pastUpdate != NULL) { /* ...loop through all past update */
- theObject = cmFindObject(toc, pastUpdate->objectID);
- /* make sure object is on touched chain */
- if (theObject)
- cmAddObjToTouchedChain(container->updatingContainer, theObject);
- pastUpdate = (PastUpdate *)cmGetNextListCell(pastUpdate);
- } /* while */
- }
-
- /*---------------------------------------------------------------*
- | totalInsertionSize - sum of the insertion size in InsertDataN |
- *---------------------------------------------------------------*
-
- This routine calculate the total size in an InsertDataN PastInsertData
- structure.
- */
-
- static CM_ULONG CM_NEAR totalInsertionSize(const PastInsertData *pastUpdate)
- {
- CM_ULONG total = pastUpdate->size;
- CM_ULONG n = pastUpdate->insertCount;
- OffsetSizePair *offsetInfoPtr = (OffsetSizePair *)(++pastUpdate);
-
- while (--n) {
- total += offsetInfoPtr->size;
- offsetInfoPtr++;
- }
- return total;
- }
-
- /*-------------------------------------------------------------*
- | putBackOldOffset - sum of the insertion size in InsertDataN |
- *-------------------------------------------------------------*
-
- Update instructions represents the changes that occurs in each container. When we
- open a chain of updating containers, the deepest containers has the value in their
- initial state and each value has a logical offset. The update within a container
- will change the values around. Before we apply the updates in the next container,
- the logical offset will be recalculated to reflect the state of the values at
- the time of the opening of the next container. The logical offsets are also
- used to generate any new update that occurs.
-
- For merge of updating containers, we need to get the logical offset before
- the top level updates have been applied. However their values has already
- been changed. Fortunately we know all the updates that occured in the top container.
- So we can reverse the process and recover the original logical offset and logical
- size of the values. Basically if there is a deleteion and add back the deleted
- size and if there is an insertion we subtract the insert size.
-
- However, there can be mulitple insert and delete updating instructions on a value, and
- we need to use all of them to recalcuate. So we will scan the past update list to
- get all the updates for the same value and use all of them. The other updates that
- we found will be removed from the list so that they would be processed for a second
- time.
- */
-
- static PastUpdate CM_NEAR *putBackOldOffset(ContainerPtr container,
- TOCObjectPtr theObject,
- PastDeleteData *pastUpdate)
- {
- TOCPropertyPtr theProperty;
- TOCValueHdrPtr theValueHdr;
- TOCValuePtr theValue;
- PastUpdate *lastDeleteUpdate, *nextUpdate;
- CM_ULONG orgOffset;
-
- theProperty = cmGetObjectProperty(theObject, pastUpdate->updateHeader.propertyID);
- if (theProperty) {
- theValueHdr = (TOCValueHdrPtr)cmGetPropertyType(theProperty, pastUpdate->updateHeader.typeID);
- if (theValueHdr) {
- /* first we can the past update list to find all updates for this value header */
- nextUpdate = (PastUpdate *)cmGetNextListCell(pastUpdate);
- lastDeleteUpdate = (PastUpdate *)pastUpdate;
- while (nextUpdate) {
- if ((nextUpdate->objectID == pastUpdate->updateHeader.objectID) &&
- (nextUpdate->propertyID == pastUpdate->updateHeader.propertyID) &&
- (nextUpdate->typeID == pastUpdate->updateHeader.typeID)) {
- if ((nextUpdate->ctlByte == DeleteData) || (nextUpdate->ctlByte == InsertData1) || (nextUpdate->ctlByte == InsertDataN))
- lastDeleteUpdate = nextUpdate;
- else
- break;
- nextUpdate = (PastUpdate *)cmGetNextListCell(nextUpdate);
- }
- else
- break;
- }
- theValue = (TOCValuePtr)cmGetListHead(&theValueHdr->valueList);
- while (theValue) {
- if (theValue->container->depth) { /* we only need to worry about old value */
- /* not all insertion and deletion will affect a value, an insertion or */
- /* occuring in an offset beyond this value will have no effect */
- /* However, we need to add up all the insertion and deletion that occurs at */
- /* an offset smaller than this value. */
- /* We first get the original logical offset into a local variable because */
- /* the comparision will be against this and we don't want to change it */
- orgOffset = theValue->logicalOffset;
- nextUpdate = ((PastUpdate *)pastUpdate);
- while (true) {
- if (nextUpdate->ctlByte == DeleteData) {
- if (orgOffset >= ((PastDeleteData *)nextUpdate)->offset)
- theValue->logicalOffset += ((PastDeleteData *)nextUpdate)->size;
- }
- else if (nextUpdate->ctlByte == InsertData1) {
- if (orgOffset > ((PastInsertData *)nextUpdate)->insertPoint)
- theValue->logicalOffset -= ((PastInsertData *)nextUpdate)->size;
- }
- else if (nextUpdate->ctlByte == InsertDataN) {
- if (orgOffset > ((PastInsertData *)nextUpdate)->insertPoint)
- theValue->logicalOffset -= totalInsertionSize((PastInsertData *)nextUpdate);
- }
- if (nextUpdate == lastDeleteUpdate)
- break;
- nextUpdate = (PastUpdate *)cmGetNextListCell(nextUpdate);
- }
- }
- theValue = (TOCValuePtr)cmGetNextListCell(theValue);
- } /* loop through all the value segments */
-
- /* Now we recalcuate the orignal logical size of the value. All insertions and */
- /* deletion will affect this value */
- while (true) {
- if (lastDeleteUpdate->ctlByte == DeleteData) {
- theValueHdr->logicalSize += ((PastDeleteData *)lastDeleteUpdate)->size;
- }
- else if (lastDeleteUpdate->ctlByte == InsertData1) {
- theValueHdr->logicalSize -= ((PastInsertData *)lastDeleteUpdate)->size;
- }
- else if (nextUpdate->ctlByte == InsertDataN) {
- theValueHdr->logicalSize -= totalInsertionSize((PastInsertData *)lastDeleteUpdate);
- }
- if ((PastDeleteData *)lastDeleteUpdate != pastUpdate) {
- /* any extra past delete that has been processed will be removed */
- nextUpdate = (PastUpdate *)cmGetPrevListCell(lastDeleteUpdate);
- CMfree(cmDeleteListCell(&container->tmpList, lastDeleteUpdate));
- lastDeleteUpdate = nextUpdate;
- }
- else
- break;
- } /* loop through all the past update for this value header */
- }
- }
- /* we may have change the list, so the next item will be different, that is why we */
- /* pass back the next item in the past update list. */
- return (PastUpdate *)cmGetNextListCell(pastUpdate);
- }
-
- /*------------------------------------------------------------*
- | cmClosePastDataGap - adjust the offset in the past history |
- *------------------------------------------------------------*
-
- When we return free space from a embedded container to the parent container, the
- offset of the values will be changed. This affects not only the offset on the TOC.
- It also affects the offset in the update objects. So we need to be able to update
- the offset in the past history to reflect the changes in the various offset.
- */
-
- void cmClosePastDataGap(ContainerPtr container, CM_ULONG offset, CM_ULONG size)
- {
- PastUpdate *pastUpdate = (PastUpdate *) cmGetListHead(&container->tmpList);
- CM_CHAR ctlByte;
- OffsetSizePair *offsetSizePair;
- CM_ULONG n;
-
- while (pastUpdate) {
- ctlByte = pastUpdate->ctlByte;
-
- switch (ctlByte) {
- case DeleteData:
- if (((PastDeleteData *)pastUpdate)->offset > offset)
- ((PastDeleteData *)pastUpdate)->offset -= size;
- break;
- case InsertData1:
- if (((PastInsertData *)pastUpdate)->offset > offset)
- ((PastInsertData *)pastUpdate)->offset -= size;
- break;
- case InsertDataN:
- n = ((PastInsertData *)pastUpdate)->insertCount;
- offsetSizePair = (OffsetSizePair *)((char *)pastUpdate + sizeof(PastUpdate)+sizeof(CM_ULONG)+sizeof(CM_ULONG));;
- while (n--) {
- if (offsetSizePair->offset > offset)
- offsetSizePair->offset -= size;
- offsetSizePair++;
- }
- break;
- } /* switch */
- pastUpdate = (PastUpdate *) cmGetNextListCell(pastUpdate);
- }
- }
-
- /*-----------------------------------------------------------------*
- | generatePastValueUpdates - generate the value update once again |
- *-----------------------------------------------------------------*
-
- When we are merging updating containers, we may need to generate update we had
- previously. We have various cases we have to deal with.
-
- If the update occurs on a value in the previous container but is not touched in the
- latest round of change, we can just generate them again.
-
- If the value no longer exists, then we can ignore the previous update, unless the
- previous update is a DeleteValue, then we have to generate the DeleteValue again.
- We also have to watch out where a create occurs after the delete.
-
- SetInfoType has some unique problem, since the type has been changed we need to
- search it differently to make sure it is still here.
-
- Certain new update will make old update superfluous, e.g. if the value is now an
- immediate value, we change ingore any past data edits.
- */
-
- static void CM_NEAR generatePastValueUpdates(TOCObjectPtr theObject, ContainerPtr container, ContainerPtr targetContainer, void *ioBuffer)
- {
- PastUpdate *pastUpdate = (PastUpdate *) cmGetListHead(&targetContainer->tmpList);
- PastUpdate *next;
- CMObjectID currPropertyID=0;
- CM_ULONG n;
- CM_CHAR ctlByte;
- TOCPropertyPtr theProperty;
- TOCValueHdrPtr theValueHdr;
- OffsetSizePair *offsetSizePair;
- CM_ULONG *baseTypePtr;
- CMBoolean becomeImmediate;
-
- while ((pastUpdate) && (theObject->objectID != pastUpdate->objectID))
- pastUpdate = (PastUpdate *) cmGetNextListCell(pastUpdate);
-
- while ((pastUpdate) && (theObject->objectID == pastUpdate->objectID)) {
- next = (PastUpdate *) cmGetNextListCell(pastUpdate);
- ctlByte = pastUpdate->ctlByte;
- if (ctlByte >= RemovedValue) {
- theProperty = cmGetObjectProperty(theObject, pastUpdate->propertyID);
- if (theProperty == NULL)
- goto generateDone; /* property is gone, no need for old update */
-
- /* If we are doing a delete value, we don't expect to see the valueHdr. We may */
- /* still see the value header in delete value because the types has been added */
- /* back. We really don't care either way and we can generate the delete value */
- /* update. For the other cases, if the value header is gone, this mean that the */
- /* type has been deleted and we can ignore the old updates since if the value is */
- /* deleted eventually, who cares about the old changes. SetInfoType is a special */
- /* case since we need to check the new type rather than the old because it is */
- /* already converted over to the new type. However in this version we do not */
- /* support merge of type changes, so we can postpone that to the future version. */
- if (ctlByte != DeleteValue) {
- theValueHdr = (TOCValueHdrPtr)cmGetPropertyType(theProperty, pastUpdate->typeID);
- if (theValueHdr == NULL)
- goto generateDone; /* value is gone, no need for old update */
- if (theValueHdr->touch) {
- /* If the value now becomes an immediate value, then we can ignore all */
- /* previous write and delete, so we want to check if it just become an */
- /* immediate value */
- becomeImmediate = (theValueHdr->touch->touchFlags & TouchedImmediate) &&
- (theValueHdr->size <= 4) &&
- (((TOCValuePtr) cmGetListHead(&theValueHdr->valueList))->flags & kCMImmediate);
- switch (ctlByte) {
- case ReplaceImmediate:
- if (becomeImmediate)
- goto generateDone; /* will have new value, discard the old value */
- break;
- case DeleteData:
- case InsertData1:
- case InsertDataN:
- if (becomeImmediate)
- goto generateDone; /* then no need to do it now */
- else if (theValueHdr->touch->touchFlags & TouchedEdited) {
- next = putBackOldOffset(targetContainer, theObject, (PastDeleteData *)pastUpdate);
- goto generateDone; /* then no need to do it now */
- }
- break;
- case SetInfoedValue:
- /* If we go from previously from a to b, now we go from b to c */
- /* the whole thing should be reorded as a to c, we change the preious */
- /* setinfo and discard the latest setinfo */
- /* However, since we are not supporting this in this new version, we don't*/
- /* need to worry about this for now and we can just use the new update and*/
- /* discard the old update */
- if (theValueHdr->touch->touchFlags & TouchedSetinfoed) {
- goto generateDone; /* then no need to do it now */
- }
- break;
- case ReplaceBaseType:
- if (theValueHdr->touch->touchFlags & TouchedBaseType)
- goto generateDone; /* will have new base, discard the old base */
- } /* switch */
- }
- }
- if (currPropertyID != pastUpdate->propertyID) { /* new property... */
- currPropertyID = pastUpdate->propertyID;
- PUT1(NewProperty , ioBuffer); /* <new prop> */
- PUT4(currPropertyID, ioBuffer); /* P */
- PUT4(pastUpdate->typeID , ioBuffer); /* T */
- } else {
- PUT1(NewType , ioBuffer); /* <new type> */
- PUT4(pastUpdate->typeID , ioBuffer); /* T */
- }
- /* We need to regenerate the update instruction again in the new merged container */
- PUT1(ctlByte, ioBuffer); /* <OpCode> */
- switch (ctlByte) {
- case SetInfoedValue:
- PUT4(((PastSetInfoedValue *)pastUpdate)->newType, ioBuffer);/* T */
- PUT4(((PastSetInfoedValue *)pastUpdate)->generation, ioBuffer);/* g */
- break;
- case DeleteData:
- PUT4(((PastDeleteData *)pastUpdate)->offset, ioBuffer); /* offset */
- PUT4(((PastDeleteData *)pastUpdate)->size, ioBuffer); /* amount */
- break;
- case InsertData1:
- PUT4(((PastInsertData *)pastUpdate)->insertPoint, ioBuffer); /* insertPoint */
- PUT4(((PastInsertData *)pastUpdate)->offset, ioBuffer); /* offset */
- PUT4(((PastInsertData *)pastUpdate)->size, ioBuffer); /* amount */
- break;
- case InsertDataN:
- PUT4(((PastInsertData *)pastUpdate)->insertPoint, ioBuffer); /* insertPoint */
- n = ((PastInsertData *)pastUpdate)->insertCount;
- PUT4(n, ioBuffer); /* n */
- offsetSizePair = (OffsetSizePair *)((char *)pastUpdate + sizeof(PastUpdate)+sizeof(CM_ULONG)+sizeof(CM_ULONG));;
- while (n--) {
- PUT4(offsetSizePair->offset, ioBuffer);
- PUT4(offsetSizePair->size, ioBuffer);
- offsetSizePair++;
- }
- break;
- case ReplaceImmediate:
- PUT1(((PastReplaceImmediate *)pastUpdate)->size, ioBuffer); /* length */
- PUT4Direct(((PastReplaceImmediate *)pastUpdate)->newImmediate, ioBuffer); /* value */
- break;
- case ReplaceBaseType:
- n = ((PastReplaceBaseType *)pastUpdate)->nBases;
- PUT2(n, ioBuffer); /* n */
- baseTypePtr = (CM_ULONG *)((char *)pastUpdate + sizeof(PastReplaceBaseType));;
- while (n--) {
- PUT4(*baseTypePtr, ioBuffer); /* base type ID */
- baseTypePtr++;
- }
- break;
- case DeleteValue:
- /* Need to do nothing beyond the OpCode */
- break;
- } /* switch */
- generateDone: CMfree(cmDeleteListCell(&targetContainer->tmpList, pastUpdate));
- }
- else { /* this is delete update, so we are done with generating past edit updates */
- break;
- }
- pastUpdate = next;
- }
-
- theObject->objectFlags &= ~EditedRecently;
- }
-
- /*-------------------------------------------------------------------*
- | generatePastDeleteUpdates - generate the delete update once again |
- *-------------------------------------------------------------------*
-
- When we are merging updating containers, we may need to generate update we had
- previously.
-
- In general we just generate the same instruction again. The only things to watch out
- for is that delete property is not necessary if the object is gone.
-
- */
-
- static void CM_NEAR generatePastDeleteUpdates(TOCObjectPtr theObject, ContainerPtr container, ContainerPtr targetContainer, void *ioBuffer)
- {
- PastUpdate *pastUpdate = (PastUpdate *) cmGetListHead(&targetContainer->tmpList);
- PastUpdate *next;
- CM_ULONG curObjectID = 0;
- TouchedListEntryPtr touch;
-
- while ((pastUpdate) && (theObject->objectID != pastUpdate->objectID))
- pastUpdate = (PastUpdate *) cmGetNextListCell(pastUpdate);
-
- while ((pastUpdate) && (theObject->objectID == pastUpdate->objectID)) {
- next = (PastUpdate *) cmGetNextListCell(pastUpdate);
- if ((pastUpdate->ctlByte == DeleteProperty1) &&
- ((theObject->objectFlags & DeletedObject) == 0)) { /* not yet deleted */
-
- /* check to see if the property is deleted, if it is then no need to do it here */
- touch = (TouchedListEntryPtr)cmGetListHead(&theObject->touchedList);
- while (touch) {
- if ((touch->propertyID == pastUpdate->propertyID)
- && (touch->touchFlags & TouchedDeletedProperty))
- break;
- touch = (TouchedListEntryPtr)cmGetNextListCell(touch);
- } /* while */
-
-
- if (!touch) { /* we don't have a touch that delete this property */
- if (curObjectID != theObject->objectID) {
- PUT1(DeleteProperty1, ioBuffer); /* <OpCode> */
- PUT4(theObject->objectID, ioBuffer); /* objectID */
- }
- else
- PUT1(DeleteProperty2, ioBuffer); /* <OpCode> */
- PUT4(pastUpdate->propertyID, ioBuffer); /* propertyID */
- }
- }
- CMfree(cmDeleteListCell(&targetContainer->tmpList, pastUpdate));
- pastUpdate = next;
- }
- }
-
- /*--------------------------------------------------------------------------*
- | generateValueUpdates - main control generate value updating instructions |
- *--------------------------------------------------------------------------*
-
- This routine is called only by cmGenerateUpdates() to control the generation of value
- updating instructions. The entire touched list chain is walked and all non-deleted
- objects on the chain have their respective touched lists processed. Property deletes are
- skipped on the touched lists. Thus all that are processed here are touched values and
- their update instructions generated.
-
- The update instructions for all the touched values of any one object are generated as data
- for a special "updating" property of that object. That property and value are defined
- here. The TOC has not yet been written, so this is permissible. It thus goes into the
- TOC like any other value and will be written to the container when the time comes. At
- open time, the TOC reader can look for this special property to get at the object's
- update list.
-
- All touched value list entries are freed after processing. Thus all that will be left
- on the touched chain are touched deleted objects and non-deleted objects with touched
- chains consisting solely of "deleted property" entries.
-
- False is returned for failure, and true for success.
-
- The instructions are generated in the following general sequences:
-
- -- --
- | ( <new prop> P T <v> [params...] ) |
- <new prop> P T <v> [params...] | < > | ... <end>
- | ( <new type> T <v> [params...] ) |
- -- --
-
- where <new prop> = NewProperty control byte
- P, T = property and type IDs (4 bytes)
- <v> = value update control byte (see genValueUpdateInstructions())
- params... = parameters as a function of <v> (see genValueUpdateInstructions())
- <new type> = NewType control byte
- <end> = EndUpdates control byte to mark the end of the updating sequence
-
- Brackets mean optional, "..." means repeating sequences of the preceding, and braces
- mean alternatives (sorry, it's the best I can do with this font).
-
- What the sequence does is generate "<v> [params...]" for every value update. However,
- only the P is generated as new properties are encountered for the current object. The
- object isn't needed since this set of instructions is just the instructions dealing with
- one object. Remember, the instructions are value data for a value belonging to the
- special "updating" property of the object.
-
- Note, the "<v> [params...]" is a recursive definition, in that the "params..." itself may
- be a "<v> [params...]" sequence.
-
- All this encoding is done to minimize generated data space for this stuff but still make
- it easy to interpret at open time. Thus NewProperty implies a PT immediately follow.
- NewType implies only a T follows. This can happen when sequences of touches for an
- object all refer to the same property. This is not the absolute minimum generation,
- however, since touch list entries are NOT sorted by property.
- */
-
- static CMBoolean CM_NEAR generateValueUpdates(ContainerPtr container, ContainerPtr targetContainer, void *ioBuffer)
- {
- TOCObjectPtr thePrevObject, theObject, nextTouchedObject;
- TouchedListEntryPtr nextTouch, touch;
- TOCValueHdrPtr updatesValueHdr;
- TOCPropertyPtr nextProperty;
- CMObjectID currPropertyID, nextPropertyID;
- CMBoolean noTouches;
- ContainerPtr updatingContainer = container->updatingContainer;
- TOCValueHdrPtr theValueHdr;
- TOCValuePtr theValue;
- CMBoolean editedRecently;
-
- /* Walk the entire touched chain looking for non-deleted touched objects... */
-
- thePrevObject = NULL; /* initially no previous object */
- theObject = updatingContainer->touchedChain; /* point to 1st touched object */
-
- while (theObject != NULL) { /* walk touched chain... */
- nextTouchedObject = theObject->nextTouchedObject; /* get ptr to next object early */
-
- if ((theObject->objectFlags & DeletedObject) == 0) {/* non-deleted objects only... */
-
- /* find out if we touch it in the most recent container */
- editedRecently = (theObject->objectFlags & EditedRecently) ? true : false;
- /* Here we have a non-deleted object. Generally, being that it's on the touched */
- /* chain, it should have a touched list. But being paranoid, we never assume */
- /* that. We do have to make sure that the touched list does not consist solely */
- /* of "deleted property" entries. At the very least we have to skip to the first */
- /* value touch on the list. Further, moves out of an object could leave that */
- /* object on the touched chain, but with no touched list entries if that was the */
- /* only thing done to the object. Those kind of objects we just want to delete. */
-
- touch = (TouchedListEntryPtr)cmGetListHead(&theObject->touchedList);
- noTouches = (CMBoolean)(touch == NULL); /* remember that list is empty */
-
- while (touch != NULL) { /* look for first value touch...*/
- nextTouch = (TouchedListEntryPtr)cmGetNextListCell(touch);
- if ((touch->touchFlags & TouchedDeletedProperty) == 0) { /* not delete property */
- if (touch->theValueHdr->theProperty) /* and we staill have property */
- if (touch->theValueHdr->container->depth == 0) {
- /* After merging, targetContainer is part of the updating container, so any */
- /* value in it does not need to generate any update instruction. */
- touch->theValueHdr->touch = NULL;
- CMfree(cmDeleteListCell(&theObject->touchedList, touch));
- }
- else
- break; /* ...got 'em! */
- else { /* property is gone, so we can ignore this change */
- touch->theValueHdr->touch = NULL;
- CMfree(cmDeleteListCell(&theObject->touchedList, touch));
- }
- }
- touch = nextTouch;
- }
-
- /* If we are "looking" at the first value touch on its touched list, we know we */
- /* have some updating instructions to generate for its object. Thus we must */
- /* create the special "updating" property for this object (basically done as a */
- /* CMNewValue()). Then we can start the generation sequence. */
-
- /* For merge we need to handle it if update is in the new container or in the */
- /* most recent container. */
- if ((touch != NULL) || editedRecently) { /* if at least 1 value touched */
- theObject = cmDefineObject(container, /* ...create "updates" property */
- theObject->objectID,
- CM_StdObjID_ValueUpdates,
- CM_StdObjID_UpdatesData,
- NULL, container->generation, 0,
- ObjectObject, &updatesValueHdr);
- if (theObject == NULL) return (false); /* failure (shit!) */
-
- cmNewBufferedOutputData(ioBuffer, updatesValueHdr); /* init for buffered output */
-
- /* We try to put out update from the past first. */
- if ((targetContainer) && (editedRecently))
- generatePastValueUpdates(theObject, container, targetContainer, ioBuffer);
- /* The property's value header is used as the "refNum" for all output operations*/
- /* done by the output routine (writeUpdate()). The updating is buffered and */
- /* written as value data for the new value. */
-
- /* Generate the appropriate value update sequences for each touched value, */
- /* skipping any further touched deleted properties we find on the list. We only */
- /* skipped initial ones above. That does not mean there aren't any further ones */
- /* on this list beyond the first touched value! */
-
- /* For entries we do process, a check must be made each time to see if the */
- /* property has changed. If it has (and the first time is always "change") then */
- /* a "<new prop> PT" sequence is generated. Otherwise a "<new type> T" sequence */
- /* is generated, since we always have a new type for each separate touched list */
- /* entry. */
-
- /* Note, there is a non-obvious subtility in the way the "P" is chosen in the */
- /* "<new prop> PT" sequence. This sequence, incombination with the object they */
- /* are data for (the "update" property value data for the "owning" object), are */
- /* used to address the receiver of the actions by the updating instructions. */
- /* That MUST address where the value is NOW. If a value wasn't moved, the */
- /* property in the touch list entry agree with where the value is now. But if */
- /* it IS moved, there is no guarantee it is (and usually won't be) in the same */
- /* property! So the property ID in the touched list entry is NOT used here to */
- /* address the value. Rather, the value header itself (it "knows" where it is) */
- /* is used to get the property ID of the property is is presently on. */
-
- /* The SPECIFIC value instruction sequences are generated separately by */
- /* genValueUpdateInstructions(). That routine assumes the "<new prop> PT" or */
- /* "<new type> T" prefix is generated here. But, as a safety check we do NOT */
- /* want to generate the prefix if genValueUpdateInstructions() CAN'T generate a */
- /* "<v> [params...]" sequence. It can't, if for some reason, the touched value */
- /* header has no values! For "removed" and "deleted value", it doesn't matter. */
- /* But for every other kind of touch it does. So, just to be safe, we do this */
- /* check here and not generate the prefix or call genValueUpdateInstructions() */
- /* if the test fails. */
-
- currPropertyID = 0; /* force first property change */
-
- while (touch != NULL) { /* look at all of 'em */
- nextTouch = (TouchedListEntryPtr)cmGetNextListCell(touch); /* need next now */
-
- if ((touch->touchFlags & TouchedDeletedProperty) == 0) /* not "deleted prop" */
- if ((touch->touchFlags & (TouchedRemoved | DeleteValue)) != 0 ||
- (touch->theValueHdr != NULL && cmCountListCells(&touch->theValueHdr->valueList) > 0)) {
- nextProperty = touch->theValueHdr->theProperty;
- if (nextProperty) { /* we still have this property */
- /* If we are merging container, values will be moved into the updating */
- /* container. Since modification to the values in the updating */
- /* is not recorded as update, then under these circumstances we do not */
- /* generate updates when there is merging. So we first check to make */
- /* sure we are not merging and the value header is not in the immediate */
- /* preceding container. */
- theValueHdr = touch->theValueHdr;
- if ((theValueHdr == NULL) ||
- ((theValueHdr->container->updatingContainer->useFlags & kCMMerging) == 0) ||
- (theValueHdr->container->depth != 1)) {
- nextPropertyID = nextProperty->propertyID;
- if (nextPropertyID != currPropertyID) { /* new property... */
- currPropertyID = nextPropertyID;
- PUT1(NewProperty , ioBuffer); /* <new prop> */
- PUT4(currPropertyID, ioBuffer); /* P */
- PUT4(touch->typeID , ioBuffer); /* T */
- } else {
- PUT1(NewType , ioBuffer); /* <new type> */
- PUT4(touch->typeID , ioBuffer); /* T */
- }
-
- /* The specific value updating sequence, "<v> [params...]", is generated*/
- /* separately. We got enough on our hands here as it is! Remember, the*/
- /* "<v> [params...]" sequence is recursive so that the "params..." may */
- /* itself container "<v> [params...]" sequences for additional updates */
- /* for the same value. */
-
- genValueUpdateInstructions(touch, ioBuffer);
- }
- }
-
- /* Now that the touched value is fully processed, we can delete its */
- /* touched entry from the touched chain. */
-
- /* we also delete it if property is gone since the update is not needed */
-
- if (touch->theValueHdr)
- touch->theValueHdr->touch = NULL;
- CMfree(cmDeleteListCell(&theObject->touchedList, touch)); /* poof! */
- } /* value update */
-
- touch = nextTouch; /* next touched chain entry */
- }; /* look at all of 'em */
-
- /* At this point all the updating instructions for all touched values for the */
- /* current object have been generated and written as value data for the special */
- /* updates" property of that object. All that remains to do is write an "end- */
- /* of-sequence" marker byte and to flush the buffer. */
-
- PUT1(EndUpdates, ioBuffer); /* add "end-of-update-sequence" */
- cmFlushOutputBuffer(ioBuffer); /* end-of-updates */
-
- /* If combine two embedded containers into one, we need to offset the values */
- if (container->mergeInSize) {
- theValue = (TOCValuePtr)cmGetListHead(&updatesValueHdr->valueList);
- while (theValue) {
- if ((theValue->flags & kCMImmediate) == 0)
- theValue->value.notImm.value += container->mergeInSize;
- theValue = (TOCValuePtr)cmGetNextListCell(theValue);
- }
- }
-
- /* The object may be removed from the touched chain if its touched chain is */
- /* empty. It won't be empty if there are any "deleted property" entries which */
- /* we skipped. */
-
- /* remove if no touched list and not touched (deleted) recently */
- if ((cmIsEmptyList(&theObject->touchedList)) && (theObject->objectFlags & DeletedRecently == 0))
- cmDeleteObjFromTouchedChain(updatingContainer, theObject, thePrevObject);
- else /* if "deleted property" entries*/
- thePrevObject = theObject; /* ...leave it on touched list */
- } else if (noTouches && ((theObject->objectFlags & DeletedRecently) == 0))
- /* if empty touch list we can remove it from the touch chain */
- cmDeleteObjFromTouchedChain(updatingContainer, theObject,thePrevObject);
- else /* if all property deletes... */
- thePrevObject = theObject; /* ...leave obj on touched chain*/
- } else /* if deleted object... */
- thePrevObject = theObject; /* ...leave it on touched chain */
-
- theObject = nextTouchedObject; /* process next touched object */
- } /* while (theObject)... */
-
- return (true); /* success */
- }
-
- /*-----------------------------------------------------------------------------*
- | makeDeleteValue - make the value that is the list of all the delete updates |
- *-----------------------------------------------------------------------------*
-
- This routine is called only by makeDeleteValue to generate the value that is
- going to hold all the instructions for the delete updates.
- */
-
- static TOCValueHdrPtr CM_NEAR makeDeleteValue(ContainerPtr container, void *ioBuffer)
- {
- TOCValueHdrPtr deletesValueHdr = NULL;
- TOCObjectPtr theTOCobject;
- void *toc;
-
- toc = container->toc; /* create TOC #1 property/value */
- container->toc = container->privateTOC; /* IN THE PRIVATE CONTAINER TOC */
-
- theTOCobject = cmDefineObject(container, /* ...create "updates" property */
- CM_StdObjID_TOC, CM_StdObjID_TOC_DeleteList,
- CM_StdObjID_UpdatesData, NULL,
- container->generation, 0,
- (ObjectObject | ProtectedObject),
- &deletesValueHdr);
-
- container->toc = toc; /* restore current TOC */
- if (theTOCobject) { /* if not failure, continue */
- container->deletesValueHdr = deletesValueHdr; /* save "refNum" for the world */
-
- cmNewBufferedOutputData(ioBuffer, deletesValueHdr);/* init for buffered output*/
- }
- return deletesValueHdr;
- }
-
- /*-----------------------------------------------------------------------------------*
- | generateDeleteUpdates - generate object and property delete updating instructions |
- *-----------------------------------------------------------------------------------*
-
- This routine is called only by cmGenerateUpdates() to control the generation of object
- and property deletion updating instructions. The entire touched list chain is walked and
- all deleted objects on the chain or non-deleted objects with a touched list are processed.
- This is the second walk of the touched chain. The first walk was done with
- generateValueUpdates() above. All that should remain on the touched chain are deleted
- objects (with possibly a touched chains of "removed" and "deleted value" entries) and
- non-deleted objects with "deleted property" entries.
-
- False is returned for failure, and true for success.
-
- The reason a deleted object can still have a touched chain of its own is that when an
- object is deleted, we explicitly leave "removed" entries on the chain but delete every-
- thing else. By doing this, values moved out of the object prior to its deletion, have
- "inserted" entries with back pointers pointing to their corresponding "removed" entry.
- If these values are then deleted, they better have a valid "removed" entry to point to
- so that it can be changed to a "deleted value" entry. That's how "deleted value" entries
- can also be on the chain. None of this causes problems, because if we see a deleted
- object on the touched chain, we generate the "delete object" updating instruction and
- ignore the touched list except to free up its memory.
-
- Note, we do not remove the remaining touched objects from the touched chain. It's
- unnecessary since we will never look at the touched chain again after this. The container
- is about to be closed.
-
- The deletion update instructions are generated as a single sequence for a value defined
- as a special "delete list" property of TOC #1 defined here. This is different from value
- updates where the update instructions for a particular object are "attached" to that
- object with a special property for it. The TOC has not yet been written, so this it is
- permissible to still create TOC objects. It thus goes into the TOC like any other value
- and will be written to the container when the time comes. At open time, the TOC reader
- can look for this special property to get at the object's update list.
-
- The deletions are processed and kept separate because they must be used at open time AFTER
- all the value updates are applied. The reason for this is mainly because of "moves". A
- value could be moved from its original position and then its original object deleted. If
- we didn't delay the deletes, there potentially may not be any source to move the value
- from!
-
- The deletion instructions are generated in the following general sequence:
-
- ( <del obj> O )
- < > ... <end>
- ( <del prop1> O P [<del prop2> P ...] )
-
- where <del obj> = DeleteObject control byte
- O, P = object and property IDs (4 bytes)
- <del prop1> = DeleteProperty1 control byte (property for another object)
- <del prop2> = DeleteProperty2 control byte (property for same object)
- <end> = EndUpdates control byte to mark the end of the updating sequence
-
- Brackets mean optional, "..." means repeating sequences of the preceding, and braces
- mean alternatives (sorry, it's the best I can do with this font).
-
- What this sequence does is generate "delete object" or "delete property" for a new or
- the same object.
- */
-
- static CMBoolean CM_NEAR generateDeleteUpdates(ContainerPtr container, ContainerPtr targetContainer, void *ioBuffer)
- {
- TOCObjectPtr theObject;
- TouchedListEntryPtr nextTouch, touch;
- TOCValueHdrPtr deletesValueHdr;
- CMBoolean deletedObject, first;
- TOCValuePtr theValue;
- CMBoolean deletedRecently;
- PastUpdate *pastUpdate;
- PastUpdate *next;
-
-
- /* Walk the entire touched chain looking for everthing remaining there. The "refNum" */
- /* for the special TOC #1 property is kept in deletesValueHdr and it is NULL until we */
- /* actually find something to generate. */
-
- deletesValueHdr = NULL; /* NULL ==> no TOC #1 value yet */
- theObject = container->updatingContainer->touchedChain;/* point to 1st touched object */
-
- while (theObject != NULL) { /* walk touched chain... */
-
- /* Find out if we did a delete update in the most recent old container, */
- deletedRecently = (theObject->objectFlags & DeletedRecently) ? true : false;
-
- /* We need to know if we're going to generate any deletion instructions for the */
- /* current object. If we have a deleted object, it's on the touched chain, so that's*/
- /* sufficient to know we need to generate a delete object instruction. On the other */
- /* hand, if we have a non-deleted object, just to be safe, we scan its touched list */
- /* to see if there are any "deleted property" entries. If there are, then we know */
- /* for that case we got something to generate. */
-
- /* Note, for deleted objects, we always make sure all the remaining touched list */
- /* entries are freed. We no longer need them. */
-
- deletedObject = (CMBoolean)((theObject->objectFlags & DeletedObject) != 0);
- touch = (TouchedListEntryPtr)cmGetListHead(&theObject->touchedList);
-
- if (deletedObject) /* if deleted object... */
- while (touch) { /* ...free the touched list */
- nextTouch = (TouchedListEntryPtr)cmGetNextListCell(touch);
- if (touch->theValueHdr)
- touch->theValueHdr->touch = NULL;
- CMfree(cmDeleteListCell(&theObject->touchedList, touch));
- touch = nextTouch;
- } /* while */
- else /* if not deleted object... */
- while (touch != NULL) { /* look for 1st deleted property*/
- if ((touch->touchFlags & TouchedDeletedProperty) != 0)
- nextTouch = (TouchedListEntryPtr)cmGetNextListCell(touch);
- /* If the property is created in the container to be merged, then after */
- /* merging it is in the update container and we don't need to generate a */
- /* delete property instruction for deleting property in the update container. */
- if ((theObject->container->updatingContainer->useFlags & kCMMerging) && (touch->typeID == 0)) {
- if (touch->theValueHdr)
- touch->theValueHdr->touch = NULL;
- CMfree(cmDeleteListCell(&theObject->touchedList, touch));
- }
- else
- break; /* ...got 'em! */
- touch = nextTouch;
- }
-
- /* If the object is in target container, then with merging the object is considered.*/
- /* to be a new object, so we don't need to generate updates. */
- if (theObject->container == targetContainer)
- deletedObject = false;
-
- /* At this point we know whether we got somthing to generate. It's either for a */
- /* deleted object, or a deleted property. If there's nothing to generate, just go */
- /* process the next object on the touched chain. */
-
- /* We need to generate update if we have delete old objects either in the new */
- /* container or the most recent updating container. */
- if ((touch != NULL) || deletedObject || deletedRecently) {/* if somthing to generate*/
-
- /* If this is the first deletion generation, create the TOC #1 "deletes" property */
- /* IN THE PRIVATE TOC. That's the TOC #1 object we're interested in. */
-
- if (deletesValueHdr == NULL) { /* if this is the first delete */
- if ((deletesValueHdr = makeDeleteValue(container, ioBuffer)) == NULL) return (false);
- } /* 1st delete */
-
- /* Generate the old update instruction once again in the new merged container */
- if ((targetContainer) && (deletedRecently))
- generatePastDeleteUpdates(theObject, container, targetContainer, ioBuffer);
-
- /* We're now ready to generate update instructions for the current touched object.*/
- /* For a deleted object we have the sequence "<del obj> O" and that's all need be */
- /* done for this object. For deleted property we have "<del prop1> OP" the first */
- /* time for this object, and "<del prop2> P" for additional touched deleted */
- /* properties on the touched list. */
-
- if (deletedObject) { /* if deleted object... */
- PUT1(DeleteObject , ioBuffer); /* <del obj> */
- PUT4(theObject->objectID, ioBuffer); /* O */
- } else if (touch) { /* if non-deleted object... */
- PUT1(DeleteProperty1 , ioBuffer); /* <del prop1> */
- PUT4(theObject->objectID, ioBuffer); /* O */
-
- first = true; /* this will gen P the 1st time */
-
- do { /* walk the touched list... */
- nextTouch = (TouchedListEntryPtr)cmGetNextListCell(touch);
-
- if ((touch->touchFlags & TouchedDeletedProperty) != 0) { /* deleted property? */
- if (!first) /* new property... */
- PUT1(DeleteProperty2, ioBuffer); /* <del prop2> */
-
- PUT4(touch->propertyID, ioBuffer); /* P */
- first = false;
- }
-
- /* Free everything remaining on the touched list just like we did for deleted */
- /* objects... */
-
- if (touch->theValueHdr)
- touch->theValueHdr->touch = NULL;
- CMfree(cmDeleteListCell(&theObject->touchedList, touch));
-
- touch = nextTouch; /* process all list entries */
- } while (touch != NULL);
- }
- } /* object or property deletions */
-
- theObject = theObject->nextTouchedObject;
- } /* while (theObject)... */
-
- /* If we are doing merging, then we may need to regenerate the delete object opcodes */
- /* These objects are not on the touch list, since they don't even exists any more. */
- /* So we need to go through the past history, get the object ID and generate the */
- /* delete update once again in the merged container. */
-
- if (targetContainer) { /* we are doing merging */
- pastUpdate = (PastUpdate *) cmGetListHead(&targetContainer->tmpList);
- while (pastUpdate) {
- next = (PastUpdate *) cmGetNextListCell(pastUpdate);
- if (pastUpdate->ctlByte == DeleteObject) {
- if (deletesValueHdr == NULL)
- if ((deletesValueHdr = makeDeleteValue(container, ioBuffer)) == NULL)
- return (false);
- PUT1(DeleteObject, ioBuffer); /* <OpCode> */
- PUT4(pastUpdate->objectID, ioBuffer); /* objectID */
- CMfree(cmDeleteListCell(&targetContainer->tmpList, pastUpdate));
- }
- pastUpdate = next;
- }
- }
-
- /* All deletion instructions (if any) have been generated. All that remains to do is */
- /* write an "end- of-sequence" marker byte and to flush the buffer. */
-
- if (deletesValueHdr) { /* if deletions generate... */
- PUT1(EndUpdates, ioBuffer); /* add "end-of-update-sequence" */
- cmFlushOutputBuffer(ioBuffer); /* ...end-of-updates */
- /* if we merge two embedded containesr, we need to offset the new values */
- if (container->mergeInSize) {
- theValue = (TOCValuePtr)cmGetListHead(&deletesValueHdr->valueList);
- while (theValue) {
- if ((theValue->flags & kCMImmediate) == 0)
- theValue->value.notImm.value += container->mergeInSize;
- theValue = (TOCValuePtr)cmGetNextListCell(theValue);
- }
- }
- }
-
- return (true); /* success */
- }
-
-
- /*--------------------------------------------------------------------*
- | cmGenerateUpdates - main control for update instruction generation |
- *--------------------------------------------------------------------*
-
- This is called at close time (CMCloseContainer()) to generate all the updating
- instructions for all objects and their values touched during an updating session. True is
- returned if successful and false otherwise (and the error reporter returns).
-
- The touched objects are on the touched chain pointed to from the container control block.
- Two passes are made over the touched chain:
-
- Pass 1: All touched values are processed. The touched list for each non-deleted
- object that is still on the touched chain (see pass 2) are processed for
- touched values. Touched properties are skipped.
-
- A special "updates" property is generated for each object involved which
- contains a value whose data is all the value updates for that object. Each
- processed touched value's touched list entry is deleted.
-
- The end of pass 1 leaves only deleted property touched list entries in
- non-deleted objects on the touched chain and touched, but deleted, objects.
- Deleted objects too can still have a touched chain of "removed" and "deleted
- objects" since such entries are retained due to the way the move and deletion
- touch algorithms work.
-
- Pass 2: All touched deleted objects and deleted properties. This is the remaining
- stuff on the touched chain and lists. The data for this is attached to a
- special TOC #1 property. The remaining touched list entries are deleted.
- Thus, at the end of pass 2, all touched list space is freed up.
- */
-
- CMBoolean cmGenerateUpdates(ContainerPtr container, ContainerPtr targetContainer)
- {
- CMBoolean success;
- void *ioBuffer = NULL;
- jmp_buf genUpdatesEnv;
-
- container = container->updatingContainer; /* make sure we're using updater */
-
- if (setjmp(genUpdatesEnv)) { /* set setjmp/longjmp environment vector*/
- Container_Disable(container);
- cmReleaseIOBuffer(ioBuffer); /* ...if longjmp taken back here... */
- ERROR1(CM_err_BadUpdateWrite,CONTAINERNAME);/* ...free buffer and report failure */
- return (false); /* ...false ==> failure */
- }
-
- /* Allocate the output buffer to more efficiently generate the update data... */
-
- ioBuffer = cmUseIOBuffer(container, UpdateBufSize, (jmp_buf *)&genUpdatesEnv);
- if (ioBuffer == NULL) return (false);
-
- /* Process both passes... */
-
- /* First pass generate the value update, the second pass generate the delete updates. */
- success = (CMBoolean)(generateValueUpdates(container, targetContainer, ioBuffer) &&
- generateDeleteUpdates(container, targetContainer, ioBuffer));
- /* Anything left over in the past history list (is there really any?) is discarded */
- if (targetContainer)
- cmFreeAllListCells(&targetContainer->tmpList, container->sessionData);
-
- /* Either of the above routines returns "false" if they fail for any reason. Either */
- /* way we give back the buffer space and then return success or failure. */
-
- cmReleaseIOBuffer(ioBuffer); /* say good bye to the output buffer */
-
- return (success); /* true ==> continue close processing */
- }
-
- #if debugUpdateList
- /*--------------------------------------------*
- | dumpOPT - dump OPT to the specified file |
- *--------------------------------------------*
-
- This routine is used for internal debugging, print the OPT address to the (open) file
- associated with the specified file variable, f.
- */
-
- static void CM_NEAR dumpOPT(FILE CM_PTR *f,
- const CM_CHAR *heading,
- CM_ULONG objectID,
- ...)
- {
- va_list otherParams;
- CM_LONG nextID;
- CM_SHORT i;
-
- fprintf(f, "|%-13.13s| %.8lX |", heading, objectID);
- va_start (otherParams, objectID);
- for (i=0;i<6;i++)
- if ((nextID=va_arg(otherParams, long)) == -1)
- break;
- else
- fprintf(f, " %.8lX |", nextID);
- va_end (otherParams);
- fprintf(f, "\n");
- }
- #endif
-
- /*---------------------------------------------------------------------*
- | makePastUpdate - put update instruction on past update history list |
- *---------------------------------------------------------------------*
-
- Put the update instruction in the past update history list for future
- use of merging.
- */
-
- static PastUpdate CM_NEAR *makePastUpdate(ContainerPtr container,
- TOCObjectPtr theObject,
- CM_ULONG propertyID,
- CM_ULONG typeID,
- CM_CHAR ctlByte,
- CM_ULONG structSize)
- {
- PastUpdate *pastUpdate = NULL;
-
- if (container->updateMergeCandidate)
- {
- pastUpdate = (PastUpdate*)
- cmAppendListCell(&container->tmpList, CMmalloc(structSize));
- if (pastUpdate)
- {
- if (ctlByte >= RemovedValue)
- theObject->objectFlags |= EditedRecently;
- else if (ctlByte != DeleteObject)
- theObject->objectFlags |= DeletedRecently;
- pastUpdate->objectID = theObject->objectID;
- pastUpdate->ctlByte = ctlByte;
- pastUpdate->propertyID = propertyID;
- pastUpdate->typeID = typeID;
- if (ctlByte != DeleteObject)
- cmAddObjToTouchedChain(theObject->container->updatingContainer, theObject);
- }
- }
- return pastUpdate;
- }
-
- /*-----------------------------------------------------------------------*
- | applyValueUpdateInstructions - value updating instruction interpreter |
- *-----------------------------------------------------------------------*
-
- This routine is called only by applyValueUpdates() to apply value updating instructions.
- applyValueUpdates() calls this routine at the point in the sequence where it sees
- "<v> [params...]", where <v> is the next control byte to be read in. The property and
- type information have already been processed and is passed to this routine along with the
- target object refNum. These uniquely address the value to be operated upon by the
- "<v> [params...]" instructions. Also passed is the object's refNum special "updates"
- property value refNum from which the updating "<v> [params...]" instructions are being
- read.
-
- Note, that there may be multiple sets of updating instructions for different values of
- the SAME property. The type IDs change, while the property ID stays the same. For
- efficiency, we search for the object's property only when property IDs change.
- applyValueUpdates() supplys a place to save the pointer, *theProperty, and it doubles as
- a switch indicating whether the property ID should be processed. NULL indicates we need
- the property pointer.
-
- The "proper" amount of updating info is consumed (read in). The "[params...]" is
- recursive in that it may itself contain "<v> [params...]" for additional updates to the
- same value. Thus we read "ahead" here until an "unrecognizable" control byte is
- encountered. That control byte is returned as the function result. If any errors are
- detected, a special "control", ErrorCtl, is returned.
-
- This routine is the symmetric counterpart to its generator, genValueUpdateInstructions().
- It interprets the updating instructions in the following formats:
-
- +---+
- RemovedValue | |
- +---+
-
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
- InsertedValue | | original O | original P | original T |
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+
- SetInfoedValue | | new T | generation |
- +---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+
- DeleteData | |starting offset| amount |
- +---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
- InsertData1 | | insert point | data offset | data length |
- +---+---+---+---+---+---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
- InsertDataN | | insert point | N | data offset1 | data length1 |
- +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
- - - -
- +---+---+---+---+---+---+---+---+
- | data offsetN | data lengthN |
- +---+---+---+---+---+---+---+---+
-
- +---+---+---+---+---+---+
- ReplaceImmediate | | l | new imm value |
- (length=l<=4) +---+---+---+---+---+---+
-
- +---+---+---+---+---+---+---+ +---+---+---+---+
- ReplaceBaseType | |n bases| base type #1 | - - - | base type #n |
- +---+---+---+---+---+---+---+ +---+---+---+---+
-
- +---+
- DeleteValue | |
- +---+
-
- See genValueUpdateInstructions() for more info on these sequences.
-
- For the purpose of merging, we want to keep a history of the update, we shall keep that
- in a list. We only need to keep the history of the most recent updating container.
-
- The list will be kept in the tmpList field of the container. This field is only used
- in the updating container, so we can overload its use.
- */
-
- static CM_UCHAR CM_NEAR applyValueUpdateInstructions(ContainerPtr container,
- TOCObjectPtr theObject,
- TOCPropertyPtr *theProperty,
- CM_ULONG propertyID,
- CM_ULONG typeID,
- void *ioBuffer)
- {
- CM_UCHAR ctlByte;
- CM_ULONG fromObjectID, fromPropertyID, fromTypeID, newTypeID, newImmediate,
- baseTypeID, offset, size, endOffset, n, insertPoint, segOffset;
- CM_USHORT nBases;
- TOCObjectPtr theFromObject, newTypeObject, theToProperty;
- TOCPropertyPtr theFromProperty;
- TOCValueHdrPtr theFromValueHdr, theValueHdr;
- TOCValuePtr theValue, theNextValue, theInsertValue, insBeforeValue;
- CMGeneration newGeneration;
- TOCValueBytes valueBytes;
- CMBoolean gotData, reLogicalize;
- CM_CHAR errStr[15], errStr1[15], errStr2[15];
- PastUpdate *pastUpdate;
- PastInsertData *pastInsert;
- OffsetSizePair *offsetInfoPtr;
- CM_ULONG *baseTypePtr;
-
- /* When applyValueUpdates() calls this routine, it only has extracted the property */
- /* and type IDs for the target value. It did nothing else. The reason is because of */
- /* moves, i.e., "inserted" and "removed" updating instruction entries. */
-
- /* For "inserted" entries, the target object, i.e., the receiver (i.e., destination) */
- /* property for the move, does NOT have to exist prior to applying the "inserted" */
- /* instruction for that object! For "removed" the last value in a source objects's */
- /* property could have been moved out, thus deleting the property in that object. */
- /* Either way, there is the potential that the property being acted upon does not */
- /* exist. */
-
- /* In all other cases, there is no value movement, so the property does have to exist.*/
- /* But applyValueUpdates() doesn't know whether the <v> in the "<v> [params...]" */
- /* sequence is for a move or not. As a matter of fact, at this point, neither do we! */
- /* So we can't set up the target value refNum until we know for sure what we have as */
- /* the first control byte. */
-
- /* If it is "inserted", it is guaranteed to be first the first in the sequence by */
- /* genValueUpdateInstructions(). This is done so that possible additional editing */
- /* operations have a value in the target object/property to work on. The move will */
- /* cause the value refNum to be set up. */
-
- /* If the control byte is "removed", it will also be the first of a sequence because */
- /* "removed" entries are never combined with any other. We never try to set or use */
- /* the value refNum for this since, in this implementation, "removed" entries are */
- /* completely ignored. */
-
- /* As soon as we know e don't have "inserted" or "removed" we can set the target value*/
- /* refNum. Since we do not yet know the first value instruction, we set a switch, */
- /* appropriately enough, the value (hdr) refNum, to indicate we need the value refNum.*/
- /* In the processing loop we set the value refNum at the "proper" time. That will be */
- /* the first time for anything except moves or after the move is done. */
-
- theValueHdr = NULL; /* don't have target value yet */
-
- /* Insert and delete updates change the data. As described in value update generation */
- /* routine, generateDataEdits(), the algorithm for determining whether NEW inserts and*/
- /* deletes occurred at close time requires that the segment's logical offsets reflect */
- /* a contiguous sequence, from 0 to the logical size. As "old" (i.e., previous) */
- /* updates are applied to the target container (which is what we're doing here), the */
- /* logical offsets and size, as set when the TOC was loaded, are invalidated. Thus at */
- /* the end of updating this value, if any inserts or deletes occured, we must */
- /* "re-logicalize" (do you like that verb?) the updates segments. To that end, the */
- /* switch, reLogicalize, is maintained and tested to see if this must be done. */
-
- reLogicalize = false; /* assume no data edits */
-
- /* Process "<v> [params...]" sequences. The "[params...]" is recursive in that it */
- /* may itself contain "<v> [params...]" for additional updates to the same value. */
- /* Thus we loop here until we hit a "<v>" that is not valid in this context. */
-
- for (;;) { /* process each sequence... */
- ctlByte = GET1(ioBuffer); /* read in next "<v>" ctl byte */
-
- /* Now that we have a control byte, we can ask if this is the first one for the */
- /* value, and if we have an "inserted" (move) entry. If it's not a move, we can */
- /* set up the value refNum. Otherwise, we wait! The property is only searched for */
- /* only when the property ID changes. For updates to multiple values for the same */
- /* property, it will not change. applyValueUpdates() indicates we have a new value */
- /* by setting *theProperty to NULL. We set *theProperty here. As long as it reamins */
- /* non-NULL there is no need to process the property ID. */
-
- if (theValueHdr == NULL && /* if no value refNum yet... */
- ctlByte != InsertedValue && /* ...and not "inserted" entry..*/
- ctlByte != RemovedValue) { /* ...and not "removed" entry...*/
- if (*theProperty == NULL) { /* ...get property if changed */
- *theProperty = cmGetObjectProperty(theObject, propertyID);
- #if !CMIGNORE_NONFATAL
- if (*theProperty == NULL) {
- Container_Disable(container);
- ERROR4(CM_err_NoUpdateProperty, CONTAINERNAME,
- cmltostr(propertyID, 1, false, errStr),
- cmltostr(theObject->objectID, 1, false, errStr1),
- "updated");
- return (ErrorCtl);
- }
- #endif
- }
-
- #if CMIGNORE_NONFATAL
- if (*theProperty)
- #endif
- theValueHdr = cmGetPropertyType(*theProperty,typeID); /* get a new type... */
- #if !CMIGNORE_NONFATAL
- if (theValueHdr == NULL) {
- Container_Disable(container);
- ERROR4(CM_err_NoUpdateType, CONTAINERNAME,
- cmltostr(typeID, 1, false, errStr),
- cmltostr(theObject->objectID, 1, false, errStr1),
- cmltostr(propertyID, 1, false, errStr2));
- return (ErrorCtl);
- }
- #endif
- }
-
- /* The value refNum (theValueHdr) is now set if we don't have a "inserted" entry. */
- /* The "inserted" entry will set it after it does the move. The move will be first */
- /* if it's there. Either way we have a value refNum which is the receiver of the */
- /* actions determined by the updating instructions we're about to process... */
-
- switch (ctlByte) { /* act upon the control byte... */
-
- /*--------------*
- | RemovedValue |
- *--------------*
-
- A "removed" instruction is provided for random access implementation that can
- explicitly access the TOC. Its counterpart is the "inserted value" instruction.
- Thus a random access implementation knows to remove the value from the object it's
- in and to insert it in the one with the "inserted value" instruction.
-
- In this implementation we load the entire TOC into memory. Therefore, a "inserted
- value" maps into a "move" and the "removed" instruction maps into a NOP so that the
- move has a source.
-
- Since a "removed" instruction can be the only instruction for the value if it is
- indeed generated, we can exit and return the next byte, should should be a control
- byte. */
-
- case RemovedValue: /* "removed" value... */
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "Removed Value", theObject->objectID, propertyID, typeID, -1);
- #endif
- /* We do not merge RemovedValue udpate in this version */
- container->updateMergeCandidate = false;
- return (GET1(ioBuffer)); /* ...exit with next byte */
-
- /*-------------*
- | DeleteValue |
- *-------------*
-
- A "deleted value" instruction requires that the target value be deleted. As with
- "removed" there can be no other instructions for the value, so again we can exit
- and return the next byte as the control byte. */
-
- case DeleteValue: /* "deleted value"... */
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "Delete Value", theObject->objectID, propertyID, typeID, -1);
- #endif
- if (theValueHdr) {
- cmMarkValueDeleted(container, theValueHdr, true);
- /* put the DeleteValue on the update history list */
- (void) makePastUpdate(container, theObject, propertyID, typeID, DeleteValue,
- sizeof(PastUpdate));
- }
- return (GET1(ioBuffer)); /* ...exit with next byte */
-
- /*---------------*
- | InsertedValue |
- *---------------*
-
- An "inserted value" in this implementation (see "removed" above) means MOVE the
- value to the current location from the source defined by the instruction's
- operands. We do the the move just like CMMoveValue() does it, by calling the common
- routine cmMoveValueHdr(). Before doing that we have to do some work to convert
- the operands, in the form of object IDs to object refNums. We never trust anything
- as we do this!
-
- Since this is not used in OpenDoc we have have look into the case of ignoring the
- non-fatal error for InsertedValue */
-
- case InsertedValue: /* "inserted" value... */
- fromObjectID = (CM_ULONG)GET4(ioBuffer); /* ...get source IDs */
- fromPropertyID = (CM_ULONG)GET4(ioBuffer);
- fromTypeID = (CM_ULONG)GET4(ioBuffer);
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "Inserted Value", theObject->objectID, propertyID,
- typeID, fromObjectID, fromPropertyID, fromTypeID, -1);
- #endif
-
- /* Convert the "from" object ID to its object refNum... */
-
- theFromObject = cmFindObject(container->toc, fromObjectID);
- if (theFromObject == NULL) {
- Container_Disable(container);
- ERROR3(CM_err_NoUpdateObject, CONTAINERNAME,
- cmltostr(fromObjectID, 1, false, errStr), "moved");
- return (ErrorCtl);
- }
-
- /* Find the property with the object... */
-
- theFromProperty = cmGetObjectProperty(theFromObject, fromPropertyID);
- if (theFromProperty == NULL) {
- Container_Disable(container);
- ERROR4(CM_err_NoUpdateProperty, CONTAINERNAME,
- cmltostr(fromPropertyID, 1, false, errStr),
- cmltostr(fromObjectID, 1, false, errStr1),
- "moved");
- return (ErrorCtl);
- }
-
- /* Find the value with the desired type ID in the "from" object's property. */
- /* This yields the "from" (source) value refNum to move. */
-
- theFromValueHdr = cmGetPropertyType(theFromProperty, fromTypeID);
- if (theFromValueHdr == NULL) {
- Container_Disable(container);
- ERROR4(CM_err_NoUpdateType, CONTAINERNAME,
- cmltostr(fromTypeID, 1, false, errStr),
- cmltostr(fromPropertyID, 1, false, errStr1),
- cmltostr(fromObjectID, 1, false, errStr2));
- return (ErrorCtl);
- }
-
- /* Finally, we need the refNum of the target property... */
-
- theToProperty = cmFindObject(container->toc, propertyID);
- if (theToProperty == NULL) {
- Container_Disable(container);
- ERROR4(CM_err_UndefUpdateObject, CONTAINERNAME, "property",
- cmltostr(theValueHdr->theProperty->propertyID, 1, false, errStr),
- "move");
- return (ErrorCtl);
- }
-
- /* Move the "from" value to the "to" (current target) object and property. This */
- /* potentially brings the property for the object into existence if this is the */
- /* first reference to the property for that object. The property object itslef, */
- /* of course, must already exist. That's the check we did just above. */
-
- theValueHdr = cmMoveValueHdr(theFromValueHdr, (CMObject)theObject, (CMProperty)theToProperty);
- if (theValueHdr == NULL) return (ErrorCtl); /* must have value refNum now */
- *theProperty = theValueHdr->theProperty; /* set the property for value */
- /* We do not merge InsertedValue udpate in this version */
- container->updateMergeCandidate = false;
-
- continue; /* look for more updates */
-
- /*----------------*
- | SetInfoedValue |
- *----------------*
-
- Change the type and generation for the target value. The instruction parameters
- contain the new values. */
-
- case SetInfoedValue: /* "set-infoed" value... */
- newTypeID = (CM_ULONG)GET4(ioBuffer); /* ...get new values */
- #if TOC1_SUPPORT
- if (container->majorVersion == 1)
- newGeneration = (CM_ULONG)GET2(ioBuffer);
- else
- newGeneration = (CM_ULONG)GET4(ioBuffer);
- #else
- newGeneration = (CM_ULONG)GET4(ioBuffer);
- #endif
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "SetInfoedValue", theObject->objectID, propertyID,
- typeID, newGeneration, newTypeID, -1);
- #endif
-
- /* Validate that the type object exists... */
-
- newTypeObject = cmFindObject(container->toc, newTypeID);
- if (newTypeObject == NULL) {
- Container_Disable(container);
- ERROR4(CM_err_UndefUpdateObject, CONTAINERNAME, "type",
- cmltostr(newTypeID, 1, false, errStr),
- "set-info");
- return (ErrorCtl);
- }
-
- /* Must have a legal generation number too... */
-
- if (newGeneration < 1) {
- Container_Disable(container);
- ERROR2(CM_err_UpdateBadGenNbr, CONTAINERNAME,
- cmltostr(newGeneration, 1, false, errStr));
- return (ErrorCtl);
- }
-
- if (theValueHdr) {
- /* We do not merge value type change in this version */
- if (theValueHdr->typeID != newTypeID)
- container->updateMergeCandidate = false;
- else {
- /* put the SetInfoedValude on the update history list */
- pastUpdate = makePastUpdate(container, theObject, propertyID, typeID,
- SetInfoedValue, sizeof(PastSetInfoedValue));
- if (pastUpdate) {
- ((PastSetInfoedValue *)pastUpdate)->newType = (CMObjectID)newTypeID;
- ((PastSetInfoedValue *)pastUpdate)->generation = newGeneration;
- }
- }
-
- theValueHdr->generation = newGeneration; /* set new generation number */
- theValueHdr->typeID = (CMObjectID)newTypeID; /* ...and type ID */
- }
-
- continue; /* look for more updates */
-
- /*------------*
- | DeleteData |
- *------------*
-
- Delete data from a value. This will cause "re-logicalizing" at the end. The
- instruction parameters contain the offset from where to start deleting and the size
- size of the deletion. The deletion is done just as CMDeleteValueData() does it, by
- calling the common routine cmDeleteSegmentData(). */
-
- case DeleteData: /* "delete data"... */
- offset = (CM_ULONG)GET4(ioBuffer); /* ...get deletion parameters.. */
- size = (CM_ULONG)GET4(ioBuffer);
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "DeleteData", theObject->objectID, propertyID,
- typeID, 0, offset, size, -1);
- #endif
-
- if (theValueHdr) {
- endOffset = offset + size - 1; /* ...get ending offset */
- if (endOffset >= theValueHdr->size)
- endOffset = theValueHdr->size - 1;
-
- cmDeleteSegmentData(theValueHdr, offset, endOffset); /* delete the data */
-
- /* put the DeleteData on the update history list */
- pastUpdate = makePastUpdate(container, theObject, propertyID, typeID,
- DeleteData, sizeof(PastDeleteData));
- if (pastUpdate) {
- ((PastDeleteData*)pastUpdate)->offset = offset;
- ((PastDeleteData*)pastUpdate)->size = endOffset - offset + 1;
- }
- reLogicalize = true; /* this requires re-logicalizing*/
- }
- continue; /* look for more updates */
-
-
- /*-------------------------*
- | InsertData1/InsertDataN |
- *-------------------------*
-
- Both data insertion cases are handled here. The case of a single insertion is the
- same as for multi-segment insertions except the count is always 1. The insertion
- is done by inserting the first segment just as CMInsertValueData() does it. The
- common routine cmInsertNewSegment() is used to insert that first segment. Here,
- we're given the offset and length to a segment. CMInsertValueData(), on the other
- hand, must write the data passed to it by the user. Either way the data is written
- by the time cmInsertNewSegment() is called. It handles worrying about splitting the
- segment being inserted into if necessary.
-
- Once the first segment is inserted, all the other segments for a multi-segment
- insert are simply linked in following the new segment.
-
- Having said all this, there is one special case. That is the case of "inserting"
- to the end of the current value (i.e., an append operation). In that case all we
- need to do is append the new segment(s) to the target value.
-
- Since NEW segments for a value are being generated here, these segments must be
- tagged, like all others, with the container that "owns" them. That is the passed
- container. Unfortunatley, standard segment creation uses the value header to get
- at updatingContainer. In the normal case, updatingContainer points to the container
- to the top-most updater to receive new updates. But at the time we're applying
- updates, we don't want that container because we're not necessarily at the top yet.
- So we have to retag the segment with the current container.
-
- Like delete, inserts cause "re-logicalizing" at the end. */
-
- case InsertData1: /* insert 1 new segment... */
- case InsertDataN: /* insert N new segments... */
-
- /* Get the insert point and number of inserts, n. For InsertData1, n is 1. */
-
- insertPoint = (CM_ULONG)GET4(ioBuffer);
- n = (ctlByte == InsertData1) ? 1UL : (CM_ULONG)GET4(ioBuffer);
- if (n == 0) {
- Container_Disable(container);
- ERROR1(CM_err_BadInsertData, CONTAINERNAME);
- return (ErrorCtl);
- }
-
- /* If we're apppending the data instead of actually inserting it, then just */
- /* prepare to append new segments to the end of the existing ones. If it is a */
- /* true insertion, then we must process the first segment specially. It must be */
- /* inserted just like CMInsertValueData() would do it. */
-
- if (theValueHdr) {
- offset = (CM_ULONG)GET4(ioBuffer); /* get 1st seg params... */
- size = (CM_ULONG)GET4(ioBuffer);
-
- /* put the InsertData on the update history list */
- pastInsert = (PastInsertData *)makePastUpdate(container,
- theObject,
- propertyID,
- typeID,
- ctlByte,
- sizeof(PastInsertData)+(n-1)*2*sizeof(CM_ULONG));
- if (pastInsert) {
- pastInsert->insertPoint = insertPoint;
- pastInsert->insertCount = n;
- pastInsert->offset = offset;
- pastInsert->size = size;
- offsetInfoPtr = (OffsetSizePair *)(++pastInsert);
- }
- else
- offsetInfoPtr = NULL;
-
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "InsertData", theObject->objectID, propertyID,
- typeID, insertPoint, offset, size, -1);
- #endif
-
- if (insertPoint == theValueHdr->size) { /* if appending... */
- gotData = true; /* indicate we have 1st params*/
- theInsertValue = (TOCValuePtr)cmGetListTail(&theValueHdr->valueList);
- } else { /* if inserting... */
-
- /* Just as in CMInsertValueData(), find out which existing segment we're going*/
- /* to insert into and the offset within that segment... */
-
- insBeforeValue = cmGetStartingValue(theValueHdr, insertPoint, &segOffset);
- if (insBeforeValue == NULL) {
- Container_Disable(container);
- ERROR2(CM_err_BadInsertOffset, CONTAINERNAME, cmltostr(insertPoint, 1, false, errStr));
- return (ErrorCtl);
- }
-
- theInsertValue = cmInsertNewSegment(theValueHdr, insBeforeValue, segOffset, offset, size);
- if (theInsertValue == NULL) return (ErrorCtl);
-
- theInsertValue->container = container; /* set proper owner of this seg */
-
- --n; /* count 1st segment processed */
- gotData = false; /* we need to read if more segs */
- }
-
- /* The remaining segments (if any) are just inserted after the one inserted */
- /* above. If we're just appeneding, all the segments are handled here. */
-
- while (n--) { /* insert/append remaining segs */
- if (!gotData) { /* get i'th segment params... */
- offset = (CM_ULONG)GET4(ioBuffer);
- size = (CM_ULONG)GET4(ioBuffer);
- if (offsetInfoPtr) {
- offsetInfoPtr->offset = offset;
- offsetInfoPtr->size = size;
- offsetInfoPtr++;
- }
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "InsertData", theObject->objectID, propertyID,
- typeID, insertPoint, offset, size, -1);
- #endif
- }
-
- if ((theValueHdr->size == 0) && (theInsertValue)) {
- /* special case, segment with empty value, just set it */
- theValueHdr->valueFlags &= ~ValueImmediate; /* it is no longer an immediate */
- theValue = theInsertValue;
- theValue->value.notImm.value = (CM_ULONG)offset;
- theValue->value.notImm.valueLen = (CM_ULONG)size;
- theValue->flags = 0;
- theValue->container = container; /* set proper owner of this seg */
- } else {
- /* Create a new value segment using the updating offset/size values... */
-
- (void)cmSetValueBytes(container, &valueBytes, Value_NotImm, offset, size);
- theValue = cmCreateValueSegment(theValueHdr, &valueBytes, 0);
- if (theValue == NULL) return (ErrorCtl);
-
- theValue->container = container; /* set proper owner of this seg */
-
- /* Insert/append the new segment and set the associated flags and value */
- /* header size... */
-
- cmInsertAfterListCell(&theValueHdr->valueList, theValue, theInsertValue);
-
- #if CMKEEP_CONTINUE_FLAG
- theInsertValue->flags = (CM_USHORT)(theInsertValue->flags | kCMContinued);
- theValueHdr->valueFlags |= ValueContinued;
- #endif
- }
-
- theValueHdr->size += size;
- gotData = false; /* need info for each new seg */
- theInsertValue = theValue; /* set to insert next segment */
- } /* while */
-
- reLogicalize = true; /* this requires re-logicalizing*/
- }
- #if CMIGNORE_NONFATAL
- else {
- while (n--) {
- (void)GET4(ioBuffer);
- (void)GET4(ioBuffer);
- }
- }
- #endif
- continue; /* look for more updates */
-
- /*------------------*
- | ReplaceImmediate |
- *------------------*
-
- Any immediate that was edited in a previous session and was recored as an update at
- close time is treated specially. Rather than recording individual updates, the
- entire immediate is output as a "replace immediate" update instruction. That
- replacement is to be done now (at open time). Replacing non-immediate by immediate
- means that the old are deleted and then replaced by the new immediate data.
-
- Note, re-logicalizing is currently not necessary since there can be only one
- segment for continued values... */
-
- case ReplaceImmediate: /* "replace immediate"... */
- if (theValueHdr) {
- theValue = (TOCValuePtr)cmGetListHead(&theValueHdr->valueList);
- if ((theValue->flags & kCMImmediate) == 0) {
- /* if version is less than 2.1, then make it 2.1 */
- if (container->minorVersion < MinorVersion)
- container->minorVersion = MinorVersion;
- if (cmCountListCells(&theValueHdr->valueList) != 1) {
- cmFreeAllListCells(&theValueHdr->valueList, SESSION);
- (void)cmSetValueBytes(container, &valueBytes, Value_Imm_Chars, 0, 0);
- cmAppendValue(theValueHdr, &valueBytes, kCMImmediate);
- theValue = (TOCValuePtr)cmGetListHead(&theValueHdr->valueList);
- } else {
- theValue->flags = kCMImmediate;
- theValueHdr->valueFlags &= ~ValueImmediate;
- }
-
- }
- size = (CM_ULONG)GET1(ioBuffer); /* get new size */
- newImmediate = (CM_ULONG)GET4Direct(ioBuffer); /* and immediate */
-
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "ReplaceImmediate", theObject->objectID, propertyID,
- typeID, 0, newImmediate, size, -1);
- #endif
-
- theValue->value.notImm.valueLen = size; /* replace the immediate... */
- theValue->value.imm.ulongValue = newImmediate;
- theValueHdr->size = size;
- theValueHdr->logicalSize = size;
- /* put the ReplaceImmediate on the update history list */
- pastUpdate = makePastUpdate(container, theObject, propertyID, typeID,
- ReplaceImmediate, sizeof(PastReplaceImmediate));
- if (pastUpdate) {
- ((PastReplaceImmediate*)pastUpdate)->size = size;
- ((PastReplaceImmediate*)pastUpdate)->newImmediate = newImmediate;
- }
- }
- #if CMIGNORE_NONFATAL
- else {
- (void)GET1(ioBuffer); /* get new size */
- (void)GET4Direct(ioBuffer); /* and immediate */
- }
- #endif
- continue; /* look for more updates */
-
- /*-----------------*
- | ReplaceBaseType |
- *-----------------*
-
- A replaced base type is similar to a replaced immediate. Base type values are
- segments of immediate data wich each immediate being a base type ID. The entire
- base type is replaced with the values from the updating instruction's parameters.
- A ssafety check here is that the referenced value is indeed a base type. */
-
- case ReplaceBaseType: /* replace base type... */
- /* There are N base type parameters. Knowing that we know how to set the value */
- /* header info... */
-
- nBases = (CM_USHORT)GET2(ioBuffer); /* get nbr of bases */
-
- #if CMIGNORE_NONFATAL
- if (!theValueHdr) {
- while (nBases--) /* skip over the unused ones */
- (void) GET4(ioBuffer);
- }
- else {
- #endif
- if (theValueHdr->typeID != CM_StdObjID_BaseTypeList) {
- Container_Disable(container);
- ERROR1(CM_err_CantRepBaseType, CONTAINERNAME);
- return (ErrorCtl);
- }
-
- #if CMKEEP_CONTINUE_FLAG
- if (nBases > 1) /* set echoed "continued" bit */
- theValueHdr->valueFlags |= ValueContinued; /* set if N > 1 */
- else
- theValueHdr->valueFlags &= ~ValueContinued; /* reset if N = 1 */
- #endif
- theValueHdr->size = nBases * sizeof(CMObjectID);/* set new total size */
-
- /* put the ReplaceBaseType on the update history list */
- pastUpdate = makePastUpdate(container, theObject, propertyID, typeID,
- ReplaceBaseType,
- sizeof(PastReplaceBaseType)+sizeof(CM_ULONG)*nBases);
- if (pastUpdate) {
- ((PastReplaceBaseType *)pastUpdate)->nBases = nBases;
- baseTypePtr = (CM_ULONG *)((char *)pastUpdate + sizeof(PastReplaceBaseType));;
- }
-
- /* replace existing segments with the new base type values as long as there are */
- /* enough segments. After that we must create new segments. */
-
- theValue = (TOCValuePtr)cmGetListHead(&theValueHdr->valueList);
-
- while (nBases--) { /* create N segments... */
- baseTypeID = (CM_ULONG)GET4(ioBuffer); /* get next base ID */
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "ReplaceBaseType", theObject->objectID, propertyID,
- typeID, baseTypeID, -1);
- #endif
-
- if (theValue == NULL) { /* create seg if no more to use */
- cmSetValueBytes(container, &valueBytes, Value_Imm_Long, baseTypeID, sizeof(CMObjectID));
- theValue = cmAppendValue(theValueHdr, &valueBytes, kCMImmediate);
- theValue->container = container;
- #if CMKEEP_CONTINUE_FLAG
- if (nBases > 0) theValue->flags |= kCMContinued;
- #endif
- theValue = NULL;
- } else { /* replace existing base type */
- theValue->value.imm.ulongValue = baseTypeID;
- #if CMKEEP_CONTINUE_FLAG
- if (nBases > 0)
- theValue->flags |= kCMContinued;
- else
- theValue->flags &= ~kCMContinued;
- #endif
- theValue = (TOCValuePtr)cmGetNextListCell(theValue);
- }
-
- /* remember each of the individual base type ID in the past history */
- if (pastUpdate) {
- *baseTypePtr = baseTypeID;
- baseTypePtr++;
- }
- }
-
- /* If the replacement used less segments than what was originally there, delete */
- /* the excess segments... */
-
- while (theValue != NULL) { /* delete excess segments... */
- theNextValue = (TOCValuePtr)cmGetNextListCell(theValue);
- CMfree(cmDeleteListCell(&theValueHdr->valueList, theValue));
- theValue = theNextValue;
- }
-
- #if CMIGNORE_NONFATAL
- } /* theValueHdr not null */
- #endif
- continue; /* look for more updates */
-
- /*----------------------*
- | Unrecognized Control |
- *----------------------*
-
- An unrecognized control byte has been encountered. If any editing was done to the
- data, the value must be "re-logicalized". The unrecognized control byte is
- returned to applyValueUpdates() to process. */
-
- default: /* unrecognized control byte... */
- if (reLogicalize) { /* if we must "re-logicalize"...*/
- size = 0; /* ...do it... */
- theValue = (TOCValuePtr)cmGetListHead(&theValueHdr->valueList);
-
- while (theValue) { /* set new logical offsets */
- theValue->logicalOffset = size;
- size += theValue->value.notImm.valueLen;
- theValue = (TOCValuePtr)cmGetNextListCell(theValue);
- }
-
- theValueHdr->logicalSize = theValueHdr->size = size; /* set new logical size */
- }
-
- return (ctlByte); /* return unrecognized ctl byte */
- } /* switch */
- } /* for */
- }
-
-
- /*-----------------------------------------------------------------------------------*
- | applyValueUpdates - main control interpreter to apply value updating instructions |
- *-----------------------------------------------------------------------------------*
-
- This routine is called only by cmApplyUpdates() to control the interpretation of value
- updating instructions. All objects that contain an updating property were put on the
- touched chain as the TOC was read. Here we walk that chain and process that property.
- For each chained object, the updating property has a value whose data is all the updating
- instructions for its object.
-
- False is returned for failure, and true for success. Read errors will longjmp back to
- cmApplyUpdates().
-
- This routine is the symmetric counterpart to its generator, generateValueUpdates(). It
- generates the updating instructions in the following sequence:
-
- -- --
- | ( <new prop> P T <v> [params...] ) |
- <new prop> P T <v> [params...] | < > | ... <end>
- | ( <new type> T <v> [params...] ) |
- -- --
-
- See generateValueUpdates() for the notation and meaning.
- */
-
- static CMBoolean CM_NEAR applyValueUpdates(ContainerPtr container, void *ioBuffer)
- {
- TOCObjectPtr theObject, nextTouchedObject;
- TOCPropertyPtr theProperty;
- TOCValueHdrPtr updatesValueHdr;
- CM_ULONG propertyID, typeID;
- register CM_UCHAR ctlByte;
- CM_CHAR errStr[15];
-
- /* Walk the entire touched chain built of objects because each such object contains a */
- /* special "updates" property detected when the TOC was read in. */
-
- theObject = container->touchedChain; /* point to 1st touched object */
-
- while (theObject != NULL) { /* walk touched chain... */
- nextTouchedObject = theObject->nextTouchedObject; /* get ptr to next object early */
-
- /* To make it easier and more efficient for us to get at the special property and */
- /* its value header (which is what we're really after), when the TOC reader put the */
- /* object on the touched chain, it set the object's refCon to point at the updating */
- /* instructions value header. That's what we use to read the instruction value */
- /* data. */
-
- updatesValueHdr = (TOCValueHdrPtr)theObject->objectRefCon; /* "updates" value hdr */
-
- if (updatesValueHdr != NULL) { /* the test is just for safety! */
- cmNewBufferedInputData(ioBuffer, updatesValueHdr, updatesValueHdr->size);
- theProperty = NULL; /* NULL means haven't processed ID*/
-
- /* At this point we have one set of updating instructions for one object. Here */
- /* we expect property and type changes. The object, of course, we know. The */
- /* SPECIFIC value updating interpretation is done separately, just like it was */
- /* generated, by genValueUpdateInstructions(). */
-
- ctlByte = GET1(ioBuffer); /* read ahead in this "scan" */
-
- while (ctlByte != EndUpdates) /* stop at <end> */
- switch (ctlByte) { /* process <new prop>/<new type> */
-
- /* The "<new prop> P T" merges with the "<new type> T" case to get the type */
- /* ID ("T"). Both these cases can be seen first in the sequence. */
-
- case NewProperty: /* <new prop> */
- propertyID = (CM_ULONG)GET4(ioBuffer); /* P */
- theProperty = NULL;
-
- /* FALL THROUGH to NewType case */
-
- /* Handle the "<new type> T" case for multiple types for the same property, */
- /* and complete the "<new prop> P T" case falling into here from above. This */
- /* yields the property/type pair to uniquely address a target value for the */
- /* current object. */
-
- case NewType: /* <new type> */
- typeID = (CM_ULONG)GET4(ioBuffer); /* T */
-
- /* A "<v> [params...]" sequence should always follow "<new prop> P T" and */
- /* "<new type> T". These sequences are processed separately by the routine */
- /* applyValueUpdateInstructions(). The "proper" amount of updating info is */
- /* consumed (read in). The "[params...]" are recursive in that it may */
- /* itself contain "<v> [params...]" for additional updates to the same */
- /* value. Thus applyValueUpdateInstructions() will be reading "ahead" until */
- /* it finds an "unrecognizable" control byte it returns that here. So we */
- /* already have the next control byte to process on return. This should be */
- /* either <new prop>, <new type>, or <end>. If any errors are detected, a */
- /* special "control", ErrorCtl, is returned so we know to quit. */
-
- ctlByte = applyValueUpdateInstructions(container, theObject, &theProperty,
- propertyID, typeID, ioBuffer);
- if (ctlByte == ErrorCtl) return (false);
-
- break; /* process next control */
-
- default: /* huh? */
- Container_Disable(container);
- ERROR3(CM_err_BadUpdateControl, CONTAINERNAME,
- cmltostr(ctlByte, -2, true, errStr), "updating");
- return (false);
- } /* switch, while */
-
- /* updatesValueHdr is the update instruction of this object. If we are going */
- /* to merge the container later, we shall be generating a new updating */
- /* instruction, and at that time we can release the space occupied by it. */
- /* So we put it in the temporary free list in case a merge would occur */
- if (container->updateMergeCandidate)
- cmAddValueToTmpFreeList(updatesValueHdr);
-
- /* All the updating instructions for the current object have now been processed.*/
- /* We now have to free the property so that a new one can be generated at close */
- /* time if this object happens to receive new updates. Besides, why horde all */
- /* that wonderful memory? */
-
- cmDeleteProperty(container, updatesValueHdr->theProperty);
- } /* updatesValueHdr */
-
- /* At this point we're done updating this object (unless it gets deleted by phase */
- /* 2 -- applyDeleteUpdates()). The object is removed from the touched chain and its*/
- /* refCon reset to NULL. By the time we process the entire touched chain, it will */
- /* end up being empty ready for recording NEW updates, if any. */
-
- cmDeleteObjFromTouchedChain(container, theObject, NULL);
-
- theObject->objectRefCon = (CMRefCon)NULL; /* put refCon back to orig state */
- theObject = nextTouchedObject; /* process next touched object */
- } /* while (theObject)... */
-
- return (true); /* we actually made it! */
- }
-
-
- /*-------------------------------------------------------------------------------------*
- | applyDeleteUpdates - interpreter to apply object and property deletion instructions |
- *-------------------------------------------------------------------------------------*
-
- This routine is called only by cmApplyUpdates() to control the interpretation of object
- and property deletion updating instructions. The deletesValueHdr is the refNum for the
- TOC #1 deletes property, if one is present. If there are no deletions, it will be NULL.
-
- False is returned for failure, and true for success. Read errors will longjmp back to
- cmApplyUpdates().
-
- This routine is the symmetric counterpart to its generator, generateDeleteUpdates(). It
- generates the updating instructions in the following sequence:
-
- ( <del obj> O )
- < > ... <end>
- ( <del prop1> O P [<del prop2> P ...] )
-
- See generateDeleteUpdates() for the notation and meaning. It is also described there
- why the deletions are kept separate and must be processed last.
- */
-
- static CMBoolean CM_NEAR applyDeleteUpdates(ContainerPtr container, void *ioBuffer)
- {
- TOCObjectPtr theObject;
- TOCPropertyPtr theProperty;
- TOCValueHdrPtr deletesValueHdr = container->deletesValueHdr;
- register CM_UCHAR ctlByte;
- CMObjectID objectID, propertyID;
- void *toc;
- CM_CHAR errStr[15];
- #if !CMIGNORE_NONFATAL
- CM_CHAR errStr1[15];
- #endif
-
- if (deletesValueHdr == NULL) /* exit if nothing to delete */
- return (true);
-
- toc = container->toc; /* make things a little faster! */
- theObject = NULL; /* NULL to safety check this */
-
- /* Process the updating instruction data of the TOC #1 "deletes" property... */
-
- cmNewBufferedInputData(ioBuffer, deletesValueHdr, deletesValueHdr->size);
-
- while ((CMBoolean)((ctlByte = GET1(ioBuffer)) != EndUpdates))
- switch (ctlByte) {
-
- /* Both "<del obj> O" and "<del prop1> O P" expect an object ID ("O") first. Thus */
- /* both cases are processed together for the O. Both these cases can be seen */
- /* first in the sequence. */
-
- case DeleteProperty1: /* <del prop1> */
- case DeleteObject: /* <del obj> */
- objectID = (CMObjectID)GET4(ioBuffer); /* O */
- theObject = cmFindObject(toc, objectID);
-
- /* If only "<del obj> O" then just delete the object and go get next control */
- /* byte. Otherwise we still need the "P" for the "<del prop1> O P" case. */
-
- if (ctlByte == DeleteObject) { /* <del obj> O ==> just delete obj*/
- if (theObject == NULL) {
- #if CMIGNORE_NONFATAL
- break;
- #else
- Container_Disable(container);
- ERROR3(CM_err_NoUpdateObject, CONTAINERNAME,
- cmltostr(objectID, 1, false, errStr), "deleted");
- return (false);
- #endif
- }
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "Delete Object", objectID, -1);
- #endif
- /* put the delete instruction on the past update history for use in merge */
- (void) makePastUpdate(container, theObject, 0, 0, DeleteObject, sizeof(PastUpdate));
- cmMarkObjectDeleted(container, theObject);
- break; /* process next control byte */
- }
-
- /* FALL THROUGH to DeleteProperty2 case */
-
- /* Get the property ID ("P") for the "<del prop1> O P" case or completely process */
- /* the "<del prop2> P" case for additional properties for the same object. In */
- /* both these cases the property for the current object is deleted. Note that a */
- /* "<del prop2> P" case should never be first in the deletion sequence, i.e., we */
- /* better have an object. */
-
- case DeleteProperty2: /* <del prop2> */
- if (theObject == NULL) {
- #if CMIGNORE_NONFATAL
- break;
- #else
- Container_Disable(container);
- ERROR3(CM_err_NoUpdateObject, CONTAINERNAME, "?", "deleted");
- return (false);
- #endif
- }
-
- propertyID = (CMObjectID)GET4(ioBuffer); /* P */
- theProperty = cmGetObjectProperty(theObject, propertyID);
- if (theProperty == NULL) {
- #if CMIGNORE_NONFATAL
- break;
- #else
- Container_Disable(container);
- ERROR4(CM_err_NoUpdateProperty, CONTAINERNAME,
- cmltostr(propertyID, 1, false, errStr),
- cmltostr(objectID, 1, false, errStr1),
- "deleted");
- return (false);
- #endif
- }
- /* put the delete instruction on the past update history for use in merge */
- (void) makePastUpdate(container, theObject, propertyID, 0, DeleteProperty1,
- sizeof(PastUpdate));
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- dumpOPT(SESSION->cmDbgFile, "Delete Property", objectID, propertyID, -1);
- #endif
- cmDeleteProperty(container, theProperty); /* delete property from its object*/
- break; /* process next control byte */
-
- default: /* huh? */
- Container_Disable(container);
- ERROR3(CM_err_BadUpdateControl, CONTAINERNAME,
- cmltostr(ctlByte, -2, true, errStr), "deleting");
- return (false);
- } /* switch, while */
-
- /* deletesValueHdr is the deleteion update instruction of this object. If we are */
- /* going to merge the container later, we shall be generating a new updating */
- /* instruction, and at that time we can release the space occupied by it. */
- /* So we put it in the temporary free list in case a merge would occur */
- if (container->updateMergeCandidate)
- cmAddValueToTmpFreeList(deletesValueHdr);
-
- /* All the deletions have been done. The TOC #1 property memory is freed since it */
- /* is not needed any longer. */
-
- cmDeleteProperty(container, deletesValueHdr->theProperty);
- container->deletesValueHdr = NULL;
-
- return (true); /* incredible? isn't it? */
- }
-
-
- /*-------------------------------------------------------------------*
- | cmApplyUpdates - apply all updates to bring a target "up to date" |
- *-------------------------------------------------------------------*
-
- This is called at open time (openTargetContainer()) to apply (execute) all the updating
- instructions for all objects and their values "touched" while loading in the non-private
- portion of an updating container's TOC. True is returned if successful and false
- otherwise (and the error reporter returns).
-
- The touched objects are on the touched chain pointed to from the passed container control
- block. These were objects containing a special "updates" property. Such a property has
- a value whose value data is all the updating instructions dealing with that object. The
- reason they were chained was so that we could get at the objects with that property and
- do it at a time when we know we have all the objects read in. During the read there could
- be references in the instructions for as-yet unread objects.
-
- To make it easier (and more efficient) to get at the "updates" property value for the
- objects on the touched chain, such objects have had their refCon set with the refNum
- (pointer) of the updating instruction value header. That was done at the same time the
- object was placed on the touched chain. We'll clear it to NULL as we process those
- objects.
-
- This routine is more-or-less the symmetric counterpart to cmGenerateUpdates() above,
- which generated the instructions we're about to read here. That generation is done in
- two passes to generate two groups of updates; first the value updates, and then the
- object/property deletions. The value updates are the instructions attached to each
- object and that is what caused the touched chain to be built that we are going to process
- here. The deletions are attached to a special TOC #1 "deletes" property. We process
- those AFTER the value updates are processed (more about this below).
-
- Thus, rather than two passes over the touched chain that we do in generating the updates,
- we have two phases here:
-
- Phase 1: All updating instructions are processed for each object on the touched chain.
- This will update all the values appropriately. Since the updating
- instructions are in terms of the original "OPT addresses" of the values we
- know how to find them. Moves can be made.
-
- Each object processed is removed from the touched chain. Thus at the end of
- phase 1, the touched chain is empty and ready to receive REAL update touches.
-
- Phase 2: The "deletes" TOC #1 property is used to get the deletion updates list. All
- object and property deletes are done at this time. This must be done last
- in order for phase 1 to work correctly. The problem is with moves interacting
- with deletes. If a value is moved from A to B, and then A (its original
- location) deleted, then the updates to that value would not have A to refer
- to. We always need the ORIGINAL "OPT" addresses of the values.
-
- A note about the container and updatingContainer pointers...
-
- At the time the non-private TOC was loaded, and when we're called here, the container
- pointer is obviously the container that just received new values and whose updates are
- to be applied to a target container. The "target" takes the form of the TOC which
- everyone is using at this point. In order to have tagged new values with this container
- and in order to be able to get at the touched chain, the updatingContainer pointer is
- temporarily set to be the SAME as the container pointer, i.e., it points to its own
- container. This is fully described in openTargetContainer(). It's sufficient to know
- that in the context of applying these updates, the container and updatingContainer
- pointers are equal in here.
- */
-
- CMBoolean cmApplyUpdates(ContainerPtr container)
- {
- CMBoolean success;
- CM_USHORT savedUseFlags;
- void *ioBuffer = NULL;
- jmp_buf applyUpdatesEnv;
-
- /* Make sure there's something to do! We must have either or both a touched chain */
- /* and deletion list. The deletion list "refNum" is saved in deletesValueHdr. */
-
- if (container->touchedChain == NULL && container->deletesValueHdr == NULL)
- return (true);
-
- #if CMDUMPTOC && debugUpdateList /* show updating instructions */
- if (SESSION->cmDbgFile) /* ...if debugging */
- fprintf(SESSION->cmDbgFile, "\nupdates in (%s)...\n\n", CONTAINERNAME);
- #endif
-
- /* "Adjust" the world to allow us to apply updates to a target container that opened */
- /* for reading. "Adjusting" here means that we pretend the container was opened for */
- /* writing. Further, we do not track free space created by deletion updates, and we */
- /* free the refNums from those deletions rather than putting them keeping track of */
- /* them as we normally do (to guard against user reuse, which we obviously don't have */
- /* here). */
-
- savedUseFlags = container->useFlags; /* save container useFlags */
- container->useFlags |= kCMWriting; /* set opened for writing to update */
- container->trackFreeSpace = false; /* stop free space tracking */
- cmChangeRefNumHandling(container->toc, false);/* allow freeing of obj/value refNums */
-
- /* Rather than check for read failures on every read, a setjmp/longjmp environment is */
- /* set up. The buffered read routine, readUpdate(), will longjmp back here if it */
- /* detects and reports an error and the error reporter returns. */
-
- if (setjmp(applyUpdatesEnv)) { /* set setjmp/longjmp environment vector*/
- cmReleaseIOBuffer(ioBuffer); /* ...read err if longjmp taken... */
- container->useFlags = savedUseFlags; /* ...restore the original useFlags */
- container->trackFreeSpace = true; /* ...track free space again */
- cmChangeRefNumHandling(container->toc,true);/* ...restore refNum tracking */
- Container_Disable(container);
- ERROR1(CM_err_BadUpdateRead, CONTAINERNAME);/* ...report error */
- return (false); /* ...I bet we have a pissed off user! */
- }
-
- /* Allocate the input buffer to more efficiently load the update data... */
-
- ioBuffer = cmUseIOBuffer(container, UpdateBufSize, (jmp_buf *)&applyUpdatesEnv);
-
- /* Process both phases... */
-
- if (ioBuffer != NULL)
- success = (CMBoolean)(applyValueUpdates(container, ioBuffer) && /* Phase 1: values */
- applyDeleteUpdates(container, ioBuffer)); /* Phase 2: deletions */
- else
- success = false;
-
- /* Either of the above routines returns "false" if they fail for any reason (excluding*/
- /* read failures, which take the setjmp/longjmp exit above). Either way we give back */
- /* the buffer space, undo the "damage" we did, and then return success or failure. */
-
- cmReleaseIOBuffer(ioBuffer); /* say good bye to the input buffer */
- container->useFlags = savedUseFlags; /* restore the original useFlags */
- container->trackFreeSpace = true; /* reenable free space tracking */
- cmChangeRefNumHandling(container->toc, true); /* restore refNum tracking */
-
- return (success); /* true ==> continue close processing */
- }
-
- CM_END_CFUNCTIONS
-